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    }