001/**
002 * Copyright 2005-2014 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 */
016package org.kuali.rice.testtools.selenium;
017
018import org.apache.commons.io.FileUtils;
019import org.openqa.selenium.By;
020import org.openqa.selenium.NoSuchWindowException;
021import org.openqa.selenium.WebDriver;
022import org.openqa.selenium.firefox.FirefoxDriver;
023import org.openqa.selenium.firefox.FirefoxProfile;
024import org.openqa.selenium.remote.DesiredCapabilities;
025
026import java.io.File;
027import java.io.IOException;
028import java.net.MalformedURLException;
029import java.util.Arrays;
030import java.util.HashMap;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Map;
034import java.util.concurrent.TimeUnit;
035
036/**
037 * Base class for Selenium Jenkins Json saving.  Originally created to work with ci.rice.kuali.org which
038 * required login in order to see results (and I couldn't get various command line tools to work with CAS).
039 * ci.kuail.org doesn't require login, so using wget or such might be a better solution as it doesn't require a browser
040 * being opened.
041 *
042 * @author Kuali Rice Team (rice.collab@kuali.org)
043 */
044public class JenkinsJsonJobResultsBase {
045
046    /**
047     * -Dbrowser.download.dir= default is Downloads in user.home.
048     */
049    public static final String BROWSER_DOWNLOAD_DIR = "browser.download.dir";
050
051    /**
052     * -Dbrowser.helperApps.neverAsk.saveToDisk= comma delimited list of MIME/Types to saveToDisk default is "application/zip".
053     */
054    public static final String BROWSER_HELPER_APPS_NEVER_ASK_SAVE_TO_DISK = "browser.helperApps.neverAsk.saveToDisk";
055
056    /**
057     * -Dcas.username= CAS username.
058     */
059    private static final String CAS_USERNAME = "cas.username";
060
061    /**
062     * -Dcas.password= CAS password.
063     */
064    private static final String CAS_PASSWORD = "cas.password";
065
066    /**
067     * -Djenkins.base.url= default is http://ci.rice.kuali.org.
068     */
069    public static final String JENKINS_BASE_URL = "jenkins.base.url";
070
071    /**
072     * REQUIRED -Djenkins.jobs= comma delimited with optional colon delimited list of jobs:jobNumbers.
073     *
074     * If no jobNumbers are included the last completed build number for the given job will be used.  If "all" is given for
075     * the jobNumbers all available job builds will be used.
076     */
077    public static final String JENKINS_JOBS = "jenkins.jobs";
078
079    /**
080     * -Djson.output.dir= default is directory java is run from, directory must exist.
081     */
082    public static final String JSON_OUTPUT_DIR = "json.output.dir";
083
084    WebDriver driver;
085    boolean passed = false;
086    String jenkinsBase;
087    String outputDirectory;
088    String[] jobsBuildsStrings;
089    Map<String, List<String>> jobsBuildsMap = new HashMap<String, List<String>>();
090    List<String> jobs = new LinkedList<String>();
091
092    String downloadDir;
093
094    public void setUp() throws MalformedURLException, InterruptedException {
095        if (System.getProperty(JENKINS_JOBS) == null) {
096            System.out.println("Don't know what jobs to retrieve.  -D" + JENKINS_JOBS + "= must be declared.");
097            System.exit(1);
098        }
099
100        jenkinsBase = System.getProperty(JENKINS_BASE_URL, "http://ci.kuali.org");
101        outputDirectory = System.getProperty(JSON_OUTPUT_DIR);
102
103        FirefoxProfile profile = new FirefoxProfile();
104        profile.setEnableNativeEvents(false);
105
106        downloadDir = System.getProperty(BROWSER_DOWNLOAD_DIR, System.getProperty("user.home") + File.separator + "Downloads");
107        // download files automatically (don't prompt)
108        profile.setPreference("browser.download.folderList", 2);
109        profile.setPreference("browser.download.manager.showWhenStarting", false);
110        profile.setPreference("browser.download.dir", downloadDir);
111        profile.setPreference(BROWSER_HELPER_APPS_NEVER_ASK_SAVE_TO_DISK, System.getProperty(BROWSER_HELPER_APPS_NEVER_ASK_SAVE_TO_DISK, "application/zip"));
112
113        DesiredCapabilities capabilities = new DesiredCapabilities();
114        capabilities.setCapability(FirefoxDriver.PROFILE, profile);
115
116        driver = new FirefoxDriver(capabilities);
117        driver.manage().timeouts().implicitlyWait(WebDriverUtils.configuredImplicityWait(), TimeUnit.SECONDS);
118        driver.get(jenkinsBase + "/login?form");
119
120        // CAS
121//        WebDriverUtils.waitFor(driver, WebDriverUtils.configuredImplicityWait(), By.id("username"),
122//                this.getClass().toString());
123//        driver.findElement(By.id("username")).sendKeys(System.getProperty(CAS_USERNAME));
124//        driver.findElement(By.id("password")).sendKeys(System.getProperty(CAS_PASSWORD));
125//        driver.findElement(By.name("submit")).click();
126//        Thread.sleep(1000);
127//
128//        exitOnLoginProblems();
129
130        // Jenkins login page (don't login, we have authenticated through CAS already
131        WebDriverUtils.waitFor(driver, WebDriverUtils.configuredImplicityWait(), By.xpath("//span[contains(text(), 'Page generated')]"), this.getClass().toString());
132
133        // setup jobs builds
134        jobsBuildsStrings = System.getProperty(JENKINS_JOBS).split("[,\\s]");
135        String job;
136        for (String jobsBuildsString : jobsBuildsStrings) {
137            if (jobsBuildsString.contains(":")) {
138                List<String> jobBuilds = Arrays.asList(jobsBuildsString.split(":"));
139                job = jobBuilds.get(0); // first item is the job name
140                jobs.add(job);
141                if (jobBuilds.size() == 2 && "all".equals(jobBuilds.get(1))) { // job:all
142                    jobsBuildsMap.put(job, fetchAllJobNumbers(job));
143                } else { // regular usage
144                    jobsBuildsMap.put(job, jobBuilds.subList(1,jobBuilds.size())); // first item is the job name
145                }
146            } else { // no jobNumbers specified, use last complete build number
147                List<String> jobBuilds = new LinkedList<String>();
148                jobBuilds.add(fetchLastCompletedBuildNumber(jobsBuildsString) + "");
149                jobs.add(jobsBuildsString);
150                jobsBuildsMap.put(jobsBuildsString, jobBuilds);
151            }
152        }
153
154        passed = true;
155    }
156
157    protected String calcOutputFile(String job, String jobNumber) {
158        String outputFile  = job + "-" + jobNumber + ".json";
159
160        if (outputDirectory != null) {
161            outputFile = outputDirectory + File.separatorChar + outputFile;
162        }
163
164        return outputFile;
165    }
166
167    protected void closeAndQuitWebDriver() {
168        if (driver != null) {
169            if (WebDriverUtils.dontTearDownPropertyNotSet() && WebDriverUtils.dontTearDownOnFailure(passed)) {
170                try {
171                    driver.close();
172                } catch (NoSuchWindowException nswe) {
173                    System.out.println("NoSuchWindowException closing WebDriver " + nswe.getMessage());
174                } finally {
175                    if (driver != null) {
176                        driver.quit();
177                    }
178                }
179            }
180        } else {
181            System.out.println("WebDriver is null for " + this.getClass().toString());
182        }
183    }
184
185    private void exitOnLoginProblems() {
186        boolean exit = false;
187        String pageSource = driver.getPageSource();
188
189        if (pageSource.contains("Username is a required field.")) {
190            System.out.println("CAS Username is a required did you set -D" + CAS_USERNAME + "=");
191            exit = true;
192        }
193
194        if (pageSource.contains("Password is a required field.")) {
195            System.out.println("CAS Password is a required did you set -D" + CAS_PASSWORD + "=");
196            exit = true;
197        }
198
199        if (pageSource.contains("The credentials you provided cannot be determined to be authentic.")) {
200            System.out.println("CAS Login Error");
201            exit = true;
202        }
203
204        if (exit) {
205            System.exit(1);
206        }
207    }
208
209    protected List<String> fetchAllJobNumbers(String job) {
210        List<String> allJobNumbers = new LinkedList<String>();
211        String url = null;
212        String jobJson;
213
214        url = jenkinsBase + "/job/" + job + "/api/json";
215
216        try {
217            jobJson = retrieveJson(url);
218
219            String jsonJobNumber;
220
221            while (jobJson.contains(("{\"number\":"))) {
222                jsonJobNumber = jobJson.substring(jobJson.indexOf("{\"number\":") + 10, jobJson.length());
223                jsonJobNumber = jsonJobNumber.substring(0, jsonJobNumber.indexOf(","));
224
225                allJobNumbers.add(jsonJobNumber);
226
227                jobJson = jobJson.substring(jobJson.indexOf("{\"number\":") + 9, jobJson.length()); // strip off while condition
228            }
229        } catch (Exception e) {
230            System.err.println("Exception fetching job " + job + " with url " + url + e.getMessage());
231        }
232
233        return allJobNumbers;
234    }
235
236    protected void fetchArchive(String job, String jobNumber) throws InterruptedException, IOException {
237        String archiveUrl = jenkinsBase;
238
239        // Views will need to be updated/improved to work with ci.kuali.org
240        if (job.contains("rice-2.4")) {
241            archiveUrl += "/view/rice-2.4";
242        }
243
244        archiveUrl += "/job/" + job + "/" + jobNumber+ "/artifact/*zip*/archive.zip";
245        driver.get(archiveUrl);
246        Thread.sleep(10000); //zip needs time to download
247        FileUtils.moveFile(new File(downloadDir + File.separator + "archive.zip"), new File(downloadDir + File.separator + job + "-" + jobNumber + ".zip"));
248    }
249
250    protected void fetchAndWriteTestReport(String job, String jobNumber) throws InterruptedException, IOException {
251        String url;
252        String json;
253        String outputFile;
254        url = jenkinsBase + "/job/" + job + "/" + jobNumber + "/testReport/api/json";
255        json = retrieveJson(url);
256
257        outputFile = calcOutputFile(job, jobNumber);
258
259        // Add some end of lines to avoid the entire file being written out as 1 line
260        json = json.replaceAll("}],", "}],\n\n");
261
262        FileUtils.writeStringToFile(new File(outputFile), json);
263    }
264
265    protected void fetchAndWriteTestReport(String job, String[] jobNumbers) throws InterruptedException, IOException {
266        for (String jobNumber : jobNumbers) {
267            fetchAndWriteTestReport(job, jobNumber);
268        }
269    }
270
271    protected String fetchLastCompletedBuildNumber(String job) throws InterruptedException {
272        String url = jenkinsBase + "/job/" + job + "/api/json";
273        String jobNumber = null;
274        try {
275            jobNumber = retrieveJson(url);
276            jobNumber = jobNumber.substring(jobNumber.indexOf("\"lastCompletedBuild\":{\"number\":") + 31, jobNumber.length());
277            jobNumber = jobNumber.substring(0, jobNumber.indexOf(","));
278        } catch (InterruptedException e) {
279            System.err.println("Exception fetching job " + job + " with url " + url + e.getMessage());
280            throw e;
281        }
282        return jobNumber;
283    }
284
285    protected String retrieveJson(String url) throws InterruptedException {
286        driver.get(url);
287        Thread.sleep(500);
288        int secondsWaited = 0;
289
290        while (!driver.getPageSource().contains("</pre></body></html>") && secondsWaited++ < 2 * WebDriverUtils.configuredImplicityWait()) {
291            Thread.sleep(1000);
292        }
293
294        String json = driver.getPageSource();
295
296        try {
297            // index out of bounds can be cause by testReport json not existing (canceled, or a job with no tests), which results in the regular html view of the job number
298            json = json.substring(json.indexOf("<pre>") + 5, json.indexOf("</pre></body></html>"));
299        } catch (IndexOutOfBoundsException iooobe) {
300            System.out.println("No JSON results for " + url + " this can be caused by jobs with no test results, from either the test being stopped, aborted or non-test jobs.");
301        }
302
303        return json;
304    }
305}