001 /** 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package edu.samplu.common; 017 018 import com.saucelabs.common.SauceOnDemandAuthentication; 019 import com.saucelabs.common.SauceOnDemandSessionIdProvider; 020 import com.saucelabs.junit.SauceOnDemandTestWatcher; 021 import com.saucelabs.saucerest.SauceREST; 022 import org.junit.Assert; 023 import org.openqa.selenium.Platform; 024 import org.openqa.selenium.WebDriver; 025 import org.openqa.selenium.ie.InternetExplorerDriver; 026 import org.openqa.selenium.remote.DesiredCapabilities; 027 import org.openqa.selenium.remote.RemoteWebDriver; 028 029 import java.io.BufferedWriter; 030 import java.io.File; 031 import java.io.FileWriter; 032 import java.io.IOException; 033 import java.net.URL; 034 import java.util.HashMap; 035 import java.util.Map; 036 037 /** 038 * Simple {@link org.openqa.selenium.remote.RemoteWebDriver} test that demonstrates how to run your Selenium tests with <a href="http://saucelabs.com/ondemand">Sauce OnDemand</a>. 039 * 040 * This test also includes the <a href="">Sauce JUnit</a> helper classes, which will use the Sauce REST API to mark the Sauce Job as passed/failed. 041 * 042 * In order to use the {@link SauceOnDemandTestWatcher}, the test must implement the {@link SauceOnDemandSessionIdProvider} interface. 043 * 044 */ 045 public class SauceLabsWebDriverHelper implements SauceOnDemandSessionIdProvider { 046 047 /** 048 * Use Saucelabs flag. For ease of disabling saucelabs without having to remove other saucelabs property settings. 049 * -Dremote.driver.saucelabs 050 */ 051 public static final String REMOTE_DRIVER_SAUCELABS_PROPERTY = "remote.driver.saucelabs"; 052 053 /** 054 * Saucelabs browser, default is Firefox. See <a href="https://saucelabs.com/docs/platforms">Saucelabs Resources</a> 055 * ff = Firefox 056 * ie = Internet Explorer 057 * chrome = Google Chrome 058 * opera = Opera 059 * android = Android 060 * safari = Safari 061 * ipad = IPad 062 * iphone = IPhone 063 * -Dsaucelabs.browser= 064 */ 065 public static final String SAUCE_BROWSER_PROPERTY = "saucelabs.browser"; 066 067 /** 068 * Suacelabs build, default is unknown. 069 * -Drice.version= 070 */ 071 public static final String SAUCE_BUILD_PROPERTY = "rice.version"; 072 073 /** 074 * Create a unix shell script to download saucelab resources, default is false 075 * Note - saucelabs history only goes back so far, if you run enough tests the resources will no longer 076 * be available for downloading. 077 * -Dsaucelabs.download.script=false 078 */ 079 public static final String SAUCE_DOWNLOAD_SCRIPT_PROPERTY = "saucelabs.download.scripts"; 080 081 /** 082 * Saucelabs idle timeout in seconds, default is 180 083 * -Dsaucelabs.idle.timeout.seconds= 084 */ 085 public static final String SAUCE_IDLE_TIMEOUT_SECONDS_PROPERTY = "saucelabs.idle.timeout.seconds"; 086 087 /** 088 * Saucelabs key, required. 089 * -Dsaucelabs.key= 090 */ 091 public static final String SAUCE_KEY_PROPERTY = "saucelabs.key"; 092 093 /** 094 * Saucelabs max duration in seconds, default is 480 095 * -Dsaucelabs.max.duration.seconds= 096 */ 097 public static final String SAUCE_MAX_DURATION_SECONDS_PROPERTY = "saucelabs.max.duration.seconds"; 098 099 /** 100 * Saucelabs platform (OS) replace spaces with underscores, default is Linux. See <a href="https://saucelabs.com/docs/platforms">Saucelabs Resources</a> 101 * -Dsaucelabs.platform= 102 */ 103 public static final String SAUCE_PLATFORM_PROPERTY = "saucelabs.platform"; 104 105 /** 106 * Saucelabs ignore security domains in IE, which can introduce flakiness, default is true. See <a href="http://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_The_does_not_work_well_on_Vista._How_do_I_get_it_to_work_as_e">InternetExplorerDriver FAQ</a> 107 * -Dsaucelabs.ie.ignore.domains=false 108 */ 109 public static final String SAUCE_IE_INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS_PROPERTY = "saucelabs.ie.ignore.domains"; 110 111 /** 112 * Saucelabs popup disable setting, default is false (not disabled). See <a href="https://saucelabs.com/docs/additional-config">DISABLE POPUP HANDLER</a> 113 * -Dsaucelabs.pop.disable= 114 */ 115 public static final String SAUCE_POPUP_PROPERTY = "saucelabs.pop.disable"; 116 117 /** 118 * Saucelabs share setting, default is share. 119 * -Dsaucelabs.share= 120 */ 121 public static final String SAUCE_SHARE_PROPERTY = "saucelabs.share"; 122 123 /** 124 * Saucelabs user 125 * -Dsaucelabs.user= 126 */ 127 public static final String SAUCE_USER_PROPERTY = "saucelabs.user"; 128 129 /** 130 * Browser Version. See <a href="https://saucelabs.com/docs/platforms">Saucelabs Resources</a> 131 * 0 or null is current version of <b>Chrome</b>. If using a browser other than Chrome this must be set else an Exception will be thrown. 132 * -Dsaucelabs.version= 133 */ 134 public static final String SAUCE_VERSION_PROPERTY = "saucelabs.browser.version"; 135 136 /** 137 * Constructs a {@link SauceOnDemandAuthentication} instance using the supplied user name/access key. To use the authentication 138 * supplied by environment variables or from an external file, use the no-arg {@link SauceOnDemandAuthentication} constructor. 139 */ 140 public SauceOnDemandAuthentication authentication = new SauceOnDemandAuthentication(System.getProperty(SAUCE_USER_PROPERTY), System.getProperty(SAUCE_KEY_PROPERTY)); 141 142 private WebDriver driver; 143 144 private String sessionId; 145 146 /** 147 * Saucelabs setup 148 * @param className 149 * @param testName 150 * @throws Exception 151 */ 152 public void setUp(String className, String testName) throws Exception { 153 if (System.getProperty(REMOTE_DRIVER_SAUCELABS_PROPERTY) == null) { // dup guard so WebDriverUtil doesn't have to be used. 154 return; 155 } 156 157 if (System.getProperty(SAUCE_USER_PROPERTY) == null || System.getProperty(SAUCE_KEY_PROPERTY) == null) { 158 Assert.fail("-D" + SAUCE_USER_PROPERTY + " and -D" + SAUCE_KEY_PROPERTY + " must be set to saucelabs user and access key."); 159 } 160 161 DesiredCapabilities capabilities = null; 162 if ("ff".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 163 capabilities = DesiredCapabilities.firefox(); 164 } else if ("ie".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 165 capabilities = DesiredCapabilities.internetExplorer(); 166 capabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, 167 System.getProperty(SAUCE_IE_INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS_PROPERTY, "true")); 168 } else if ("chrome".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 169 capabilities = DesiredCapabilities.chrome(); 170 } else if ("opera".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 171 capabilities = DesiredCapabilities.opera(); 172 } else if ("android".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 173 capabilities = DesiredCapabilities.android(); 174 } else if ("safari".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 175 capabilities = DesiredCapabilities.safari(); 176 } else if ("ipad".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 177 capabilities = DesiredCapabilities.ipad(); 178 } else if ("iphone".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 179 capabilities = DesiredCapabilities.iphone(); 180 } else { 181 capabilities = DesiredCapabilities.firefox(); 182 } 183 184 String version = System.getProperty(SAUCE_VERSION_PROPERTY); 185 if (version == null || "0".equals(version)) { // Blank or 0 leaves version blank for use with chrome 186 187 if (!"chrome".equalsIgnoreCase(System.getProperty(SAUCE_BROWSER_PROPERTY))) { 188 throw new RuntimeException("Blank or 0 version for a browser not chrome " + System.getProperty(SAUCE_BROWSER_PROPERTY)); 189 } 190 191 capabilities.setCapability("version", ""); // saucelabs requires blank for chrome (latest version) 192 } else { 193 capabilities.setCapability("version", version); // saucelabs requires blank for chrome (latest version) 194 } 195 196 capabilities.setCapability("platform", System.getProperty(SAUCE_PLATFORM_PROPERTY, Platform.UNIX.toString()).replaceAll("_", " ")); 197 capabilities.setCapability("idle-timeout", Integer.parseInt(System.getProperty(SAUCE_IDLE_TIMEOUT_SECONDS_PROPERTY, "180"))); 198 capabilities.setCapability("max-duration", Integer.parseInt(System.getProperty(SAUCE_MAX_DURATION_SECONDS_PROPERTY, "480"))); 199 capabilities.setCapability("name", className + "." + testName + "-" + ITUtil.DTS); 200 capabilities.setCapability("disable-popup-handler", System.getProperty(SAUCE_POPUP_PROPERTY, "false")); 201 capabilities.setCapability("public", System.getProperty(SAUCE_SHARE_PROPERTY, "share")); 202 203 this.driver = new RemoteWebDriver( 204 new URL("http://" + authentication.getUsername() + ":" + authentication.getAccessKey() + "@ondemand.saucelabs.com:80/wd/hub"), 205 capabilities); 206 this.sessionId = ((RemoteWebDriver)driver).getSessionId().toString(); 207 208 // TODO it would be better to do these at tear down, passing state could then be included in names, but requires more parameters 209 if ("true".equals(System.getProperty(SAUCE_DOWNLOAD_SCRIPT_PROPERTY, "false"))) { 210 try { 211 String dir = determineSaveDir(className, testName); 212 String resources = "mkdir " + dir + " ; cd " + dir + " ; \n" 213 + curlSaveResourceString(className, testName, "selenium-server.log") + " ; \n" 214 + curlSaveResourceString(className, testName, "video.flv") + " ; \n" 215 // + wgetnSaveResourceString(className, testName) + " ; \n" 216 + "cd ../\n"; 217 System.out.println(resources); 218 writeFile("SauceLabsResources" + dir + ".sh", resources); 219 } catch (Exception e) { 220 System.out.println("Exception while writing SauceLabsResources.sh " + e.getMessage()); 221 System.out.println(curlSaveResourceString(className, testName, "selenium-server.log")); 222 System.out.println(curlSaveResourceString(className, testName, "video.flv")); 223 // System.out.println(curlSaveResourceString(className, testName, "XXXXscreenshot.png (where XXXX is a number between 0000 and 9999)")); // TODO 224 } 225 } 226 } 227 228 /** 229 * Do Suacelabs related teardown things. Mostly flag the tests as passed or failed. 230 * @param passed 231 * @param sessionId 232 * @param sauceUser 233 * @param sauceKey 234 * @throws Exception 235 */ 236 public static void tearDown(boolean passed, String sessionId, String sauceUser, String sauceKey) throws Exception { 237 if (sessionId != null && System.getProperty(REMOTE_DRIVER_SAUCELABS_PROPERTY) != null) { // dup guard so WebDriverUtil doesn't have to be used 238 SauceREST client = new SauceREST(sauceUser, sauceKey); 239 /* Using a map of udpates: 240 * (http://saucelabs.com/docs/sauce-ondemand#alternative-annotation-methods) 241 */ 242 Map<String, Object> updates = new HashMap<String, Object>(); 243 updates.put("passed", passed); 244 updates.put("build", System.getProperty(SAUCE_BUILD_PROPERTY, "unknown")); 245 client.updateJobInfo(sessionId, updates); 246 247 if (passed) { 248 System.out.println("Registering session passed " + sessionId); 249 client.jobPassed(sessionId); 250 } else { 251 System.out.println("Registering session failed " + sessionId); 252 client.jobFailed(sessionId); 253 } 254 255 Thread.sleep(5000); // give the client message a chance to get processed on saucelabs side 256 } 257 } 258 259 private String curlSaveResourceString(String className, String testName, String resource) { 260 return "curl -o " + deriveResourceBaseNames(className, testName, resource) 261 + " -u " + authentication.getUsername() + ":" + authentication.getAccessKey() 262 + " http://saucelabs.com/rest/" + authentication.getUsername()+ "/jobs/" + sessionId + "/results/" + resource; 263 } 264 265 private String deriveResourceBaseNames(String className, String testName, String resource) { 266 return className + "." + testName + "-" 267 + System.getProperty(SAUCE_PLATFORM_PROPERTY, Platform.UNIX.toString()) + "-" 268 + System.getProperty(SAUCE_BROWSER_PROPERTY) + "-" 269 + System.getProperty(SAUCE_VERSION_PROPERTY) + "-" 270 + System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY, "admin") + "-" 271 + System.getProperty(SAUCE_BUILD_PROPERTY, "unknown_build") + "-" 272 + ITUtil.DTS + "-" 273 + resource; 274 } 275 276 /** 277 * Returns the driver 278 * @return WebDriver 279 */ 280 public WebDriver getDriver() { 281 return driver; 282 } 283 284 @Override 285 public String getSessionId() { 286 return sessionId; 287 } 288 289 // Seems like sceenshot downloading has changed, this doesn't work anymore 290 private String wgetnSaveResourceString(String className, String testName) { 291 String dir = determineSaveDir(className, testName); 292 // http://www.jwz.org/hacks/wgetn 293 return "wgetn https://saucelabs.com/rest/" + authentication.getUsername()+ "/jobs/" 294 + sessionId + "/results/%04dscreenshot.jpg 0 50"; 295 } 296 297 private String determineSaveDir(String className, String testName) { 298 String dir = deriveResourceBaseNames(className, testName, ""); 299 dir = dir.substring(0, dir.length() -1); 300 return dir; 301 } 302 303 private void writeFile(String fileName, String content) throws IOException { 304 File file = new File(fileName); 305 306 if (!file.exists()) { 307 file.createNewFile(); 308 } 309 310 FileWriter fw = new FileWriter(file.getAbsoluteFile()); 311 BufferedWriter bw = new BufferedWriter(fw); 312 bw.write(content); 313 bw.flush(); 314 bw.close(); 315 } 316 }