View Javadoc
1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package edu.samplu.common;
17  
18  import com.saucelabs.common.SauceOnDemandAuthentication;
19  import com.saucelabs.common.SauceOnDemandSessionIdProvider;
20  import com.saucelabs.junit.SauceOnDemandTestWatcher;
21  import com.saucelabs.saucerest.SauceREST;
22  import org.junit.Assert;
23  import org.openqa.selenium.Platform;
24  import org.openqa.selenium.WebDriver;
25  import org.openqa.selenium.ie.InternetExplorerDriver;
26  import org.openqa.selenium.remote.DesiredCapabilities;
27  import org.openqa.selenium.remote.RemoteWebDriver;
28  
29  import java.io.BufferedWriter;
30  import java.io.File;
31  import java.io.FileWriter;
32  import java.io.IOException;
33  import java.net.URL;
34  import java.util.HashMap;
35  import java.util.Map;
36  
37  /**
38   * 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>.
39   *
40   * 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.
41   *
42   * In order to use the {@link SauceOnDemandTestWatcher}, the test must implement the {@link SauceOnDemandSessionIdProvider} interface.
43   *
44   */
45  public class SauceLabsWebDriverHelper implements SauceOnDemandSessionIdProvider {
46  
47      /**
48       * Use Saucelabs flag.  For ease of disabling saucelabs without having to remove other saucelabs property settings.
49       * -Dremote.driver.saucelabs
50       */
51      public static final String REMOTE_DRIVER_SAUCELABS_PROPERTY = "remote.driver.saucelabs";
52  
53      /**
54       * Saucelabs browser, default is Firefox.  See <a href="https://saucelabs.com/docs/platforms">Saucelabs Resources</a>
55       * ff = Firefox
56       * ie = Internet Explorer
57       * chrome = Google Chrome
58       * opera = Opera
59       * android = Android
60       * safari = Safari
61       * ipad = IPad
62       * iphone = IPhone
63       * -Dsaucelabs.browser=
64       */
65      public static final String SAUCE_BROWSER_PROPERTY = "saucelabs.browser";
66  
67      /**
68       * Suacelabs build, default is unknown.
69       * -Drice.version=
70       */
71      public static final String SAUCE_BUILD_PROPERTY = "rice.version";
72  
73      /**
74       * Create a unix shell script to download saucelab resources, default is false
75       * Note - saucelabs history only goes back so far, if you run enough tests the resources will no longer
76       * be available for downloading.
77       * -Dsaucelabs.download.script=false
78       */
79      public static final String SAUCE_DOWNLOAD_SCRIPT_PROPERTY = "saucelabs.download.scripts";
80  
81      /**
82       * Saucelabs idle timeout in seconds, default is 180
83       * -Dsaucelabs.idle.timeout.seconds=
84       */
85      public static final String SAUCE_IDLE_TIMEOUT_SECONDS_PROPERTY = "saucelabs.idle.timeout.seconds";
86  
87      /**
88       * Saucelabs key, required.
89       * -Dsaucelabs.key=
90       */
91      public static final String SAUCE_KEY_PROPERTY = "saucelabs.key";
92  
93      /**
94       * Saucelabs max duration in seconds, default is 480
95       * -Dsaucelabs.max.duration.seconds=
96       */
97      public static final String SAUCE_MAX_DURATION_SECONDS_PROPERTY = "saucelabs.max.duration.seconds";
98  
99      /**
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 }