Java tutorial
/* * Copyright 2011-2012 SugarCRM Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * Please see the License for the specific language governing permissions and * limitations under the License. */ package org.sugarcrm.voodoodriver; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.util.Date; import org.apache.commons.io.FilenameUtils; import org.openqa.selenium.Alert; public class Reporter { private String resultDir = ""; private String reportLog = null; private FileOutputStream reportFD = null; private int Blocked = 0; private int Exceptions = 0; private int FailedAsserts = 0; private int PassedAsserts = 0; private int OtherErrors = 0; private int WatchDog = 0; private String LineSeparator = null; private Browser browser = null; private boolean isRestart = false; private String testName = null; /** * When to save the current HTML page. Keys are the possible * events, and the values are true or false. */ private VDDHash saveHtmlOn; /** * Saved HTML page file name index. */ private int saveHtmlIdx = 0; /** * When to take a screenshot. Keys are the possible events, and * the values are true or false. */ private VDDHash screenshotOn; /** * Screenshot file name index. */ private int screenshotIdx = 0; /** * Whether to terminate the current thread on error. */ private boolean haltOnFailure = false; /** * Instantiate a Reporter object. * * @param reportName * @param resultDir */ public Reporter(String reportName, String resultDir, VDDHash config) { Date now = new Date(); String frac = String.format("%1$tN", now); String date_str = String.format("%1$tm-%1$td-%1$tY-%1$tI-%1$tM-%1$tS", now); frac = frac.subSequence(0, 3).toString(); date_str += String.format("-%s", frac); this.LineSeparator = System.getProperty("line.separator"); if (resultDir != null) { File dir = new File(resultDir); if (!dir.exists()) { dir.mkdirs(); } this.resultDir = resultDir; } else { this.resultDir = System.getProperty("user.dir"); } reportLog = this.resultDir + "/" + reportName + "-" + date_str + ".log"; reportLog = FilenameUtils.separatorsToSystem(reportLog); System.out.printf("ReportFile: %s\n", reportLog); try { reportFD = new FileOutputStream(reportLog); } catch (java.io.FileNotFoundException e) { System.err.println("(!)Unable to create report file: " + e); } /* Initialize screenshot and savehtml events */ String ssEvents[] = { "warning", "error", "assertfail", "exception", "watchdog" }; this.saveHtmlOn = new VDDHash(); this.screenshotOn = new VDDHash(); for (String ssEvent : ssEvents) { this.saveHtmlOn.put(ssEvent, false); this.screenshotOn.put(ssEvent, false); } this.haltOnFailure = (Boolean) config.get("haltOnFailure"); } public void setTestName(String name) { this.testName = name; } public void setIsRestTest(boolean restart) { this.isRestart = restart; } public void setBrowser(Browser browser) { this.browser = browser; } /** * Set the events for saving the current HTML page. * * Input is expected to a string that is either "all" or a * comma-separated list of events. Valid events are: * * <ul><li>warning</li> * <li>error</li> * <li>assertfail</li> * <li>exception</li> * <li>watchdog</li></ul> * * @param events list of events */ public void setSaveHTML(String events) { if (events.equals("all")) { for (String key : this.saveHtmlOn.keySet()) { this.saveHtmlOn.put(key, true); } } else { for (String event : events.split(",")) { if (!this.saveHtmlOn.containsKey(event)) { System.out.println("(!)Unrecognized event in savehtml list: " + event); continue; } this.saveHtmlOn.put(event, true); } } } /** * Set the events for taking a screenshot. * * Input is expected to a string that is either "all" or a * comma-separated list of events. Valid events are: * * <ul><li>warning</li> * <li>error</li> * <li>assertfail</li> * <li>exception</li> * <li>watchdog</li></ul> * * @param events list of events */ public void setScreenshot(String events) { if (events.equals("all")) { for (String key : this.screenshotOn.keySet()) { this.screenshotOn.put(key, true); } } else { for (String event : events.split(",")) { if (!this.screenshotOn.containsKey(event)) { System.out.println("(!)Unrecognized event in screenshot list: " + event); continue; } this.screenshotOn.put(event, true); } } } public String getLogFileName() { return this.reportLog; } public TestResults getResults() { TestResults result = null; Integer res = 0; result = new TestResults(); result.put("testlog", this.reportLog); result.put("blocked", this.Blocked); result.put("exceptions", this.Exceptions); result.put("failedasserts", this.FailedAsserts); result.put("passedasserts", this.PassedAsserts); result.put("watchdog", this.WatchDog); result.put("errors", this.OtherErrors); result.put("isrestart", this.isRestart); if (this.Blocked > 0 || this.Exceptions > 0 || this.FailedAsserts > 0 || this.OtherErrors > 0) { res = -1; } result.put("result", res); return result; } /** * Terminate the current thread. * * There's a long discussion in the Java documentation about why * using Thread.stop() is a terrible, terrible thing. However, the * alternate offered, in combination with the klunky way Java * handles exceptions means that any program wanting to terminate a * thread abnormally must design this ability in from the ground up * -- not the case with VDD. Consequently, despite the "dangers" * involved in stopping a running thread, it's done here. It won't * matter anyways as VDD will be terminating itself shortly. */ private void killTestThread() { System.err.println("(!)Error seen and haltOnFailure set: terminating."); Thread.currentThread().stop(); } private String replaceLineFeed(String str) { str = str.replaceAll("\n", "\\\\n"); return str; } private void _log(String msg) { Date now = new Date(); String frac = String.format("%1$tN", now); String date_str = String.format("%1$tm/%1$td/%1$tY-%1$tI:%1$tM:%1$tS", now); frac = frac.subSequence(0, 3).toString(); date_str += String.format(".%s", frac); msg = replaceLineFeed(msg); String logstr = "[" + date_str + "]" + msg + this.LineSeparator; if (msg.isEmpty()) { msg = "Found empty message!"; } System.out.printf("%s\n", msg); try { this.reportFD.write(logstr.getBytes()); } catch (java.io.IOException e) { System.err.println("(!)Error writing to report file: " + e); } } public void closeLog() { try { this.reportFD.close(); } catch (java.io.IOException e) { System.err.println("(!)Error closing report file: " + e); } this.reportFD = null; } public void Log(String msg) { this._log("(*)" + msg); } public void Warn(String msg, boolean savePage) { this._log("(W)" + msg); if (savePage && (Boolean) this.saveHtmlOn.get("warning")) { this.SavePage(); } if ((Boolean) this.screenshotOn.get("warning")) { this.screenshot(); } } public void Warn(String msg) { Warn(msg, true); } public void ReportError(String msg) { this._log(String.format("(!)%s", msg)); this.OtherErrors += 1; if ((Boolean) this.saveHtmlOn.get("error")) { this.SavePage(); } if ((Boolean) this.screenshotOn.get("error")) { this.screenshot(); } if (this.haltOnFailure) { killTestThread(); } } public void ReportWatchDog(long seconds) { this._log(String.format("(!)Test watchdogged out after: '%d' seconds!", seconds)); this.WatchDog = 1; if ((Boolean) this.saveHtmlOn.get("watchdog")) { this.SavePage(); } if ((Boolean) this.screenshotOn.get("watchdog")) { this.screenshot(); } } public void ReportBlocked() { this.Blocked = 1; } /** * Accept and log an unhandled alert. * * <p>Should the test script leave an alert up, an * UnhandledAlertException will eventually be thrown by selenium. * While from a test's point of view, the timing of this exception * seems non-deterministic, it is thrown when the first "do * something" call is made to selenium after the alert's appearance * (just retrieving data from the page won't trigger it).</p> * * <p>Since the exception it generates needs to be logged and * logging an exception can fetch the page source which will throw * an exception, the alert is handled by the Reporter class which * has ways to log exceptions without triggering a page fetch.</p> * * @param e the UnhandledAlertException */ public void unhandledAlert(org.openqa.selenium.WebDriverException e) { try { Alert alert = this.browser.getDriver().switchTo().alert(); String alertText = alert.getText(); /* * Presumably, accept will be more likely to Do The Right * Thing(TM) WRT getting rid of alerts and moving on, but it * depends entirely on how the page in question is written. */ alert.accept(); this._log("(!)Unhandled alert found and dismissed. Alert text is \"" + alertText + "\""); } finally { justReportTheException(e); } } /** * Log the exception only. * * This helper routine is needed since some of the Reporter methods * could need to report an exception. * */ private void justReportTheException(Exception e) { this.Exceptions += 1; if (e.getMessage() == null) { this._log("(!)ReportException: Exception message is null!"); } else { this._log("(!)" + e.getMessage().replaceAll("\\n", " ")); } String bt = "--Exception Backtrace: "; for (StackTraceElement el : e.getStackTrace()) { bt += "--" + el.toString(); } this._log("(!)" + bt); } /** * Log an exception. * * This method formats a java exception into a log entry. Both the * message and the stack trace are reformatted and printed to the * SODA log file and the console. * * @param e the exception to report */ public void ReportException(Exception e) { justReportTheException(e); if ((Boolean) this.saveHtmlOn.get("exception")) { this.SavePage(); } if ((Boolean) this.screenshotOn.get("exception")) { this.screenshot(); } if (this.haltOnFailure) { killTestThread(); } } public boolean Assert(String msg, boolean state, boolean expected) { boolean result = false; String status = ""; if (state == expected) { this.PassedAsserts += 1; status = "(*)Assert Passed: "; result = true; } else { this.FailedAsserts += 1; status = "(!)Assert Failed: "; result = false; } status = status.concat(msg); this._log(status); if (result == false && (Boolean) this.saveHtmlOn.get("assertfail")) { this.SavePage(); } if (result == false && (Boolean) this.screenshotOn.get("assertfail")) { this.screenshot(); } if (result == false && this.haltOnFailure) { killTestThread(); } return result; } public boolean Assert(String search, String src) { TextFinder f = new TextFinder(search); boolean found = f.find(src); if (found) { this.PassedAsserts += 1; this.Log("Assert Passed, found: '" + search + "'."); } else { this.FailedAsserts += 1; this._log("(!)Assert Failed for: '" + search + "'!"); } if (!found && (Boolean) this.saveHtmlOn.get("assertfail")) { this.SavePage(); } if (!found && (Boolean) this.screenshotOn.get("assertfail")) { this.screenshot(); } if (!found && this.haltOnFailure) { killTestThread(); } return found; } public boolean AssertNot(String search, String src) { TextFinder f = new TextFinder(search); boolean found = f.find(src); if (found) { this.FailedAsserts += 1; this._log("(!)Assert Failed, found unexpected text: '" + search + "'."); } else { this.PassedAsserts += 1; this.Log("Assert Passed, did not find: '" + search + "'!"); } if (found && (Boolean) this.saveHtmlOn.get("assertfail")) { this.SavePage(); } if (found && (Boolean) this.screenshotOn.get("assertfail")) { this.screenshot(); } if (found && this.haltOnFailure) { killTestThread(); } return !found; } /** * Create a file name. * * Use the specified directory, file name root, and file index to * create the filename. The directory is assumed to be relative to * resultDir. * * @param dir the directory in which to create the file name * @param file the file name root * @param idx one-up index of this file * @param ext file extension * @return path and file name */ private String makeFilename(String dir, String file, int idx, String ext) { String test = ""; String outfile = this.resultDir + "/" + dir; File checkDir = new File(outfile); if (!checkDir.exists()) { checkDir.mkdir(); } if (this.testName != null) { File tmp = new File(this.testName); test = tmp.getName(); test = String.format("%s-", test.substring(0, test.length() - 4)); } outfile += String.format("/%s%s-%d.%s", test, file, idx, ext); return FilenameUtils.separatorsToSystem(outfile); } /** * Save the current HTML page. */ public void SavePage() { String htmlFile = makeFilename("saved-html", "savedhtml", this.saveHtmlIdx, "html"); this.saveHtmlIdx += 1; String pageSource = this.browser.getPageSource(); try { File f = new File(htmlFile); BufferedWriter bw = new BufferedWriter(new FileWriter(f)); bw.write(pageSource); bw.close(); this.Log(String.format("HTML Saved: %s", htmlFile)); } catch (java.io.IOException e) { this.justReportTheException(e); } } /** * Take a screenshot of the current page. */ public void screenshot() { String screenshotFile = makeFilename("screenshots", "screenshot", this.screenshotIdx, "png"); this.screenshotIdx += 1; Utils.takeScreenShot(screenshotFile, this, false); } /** * Clean up on object destruction. * * This method simply makes sure that the output file handle is * properly closed. */ protected void finalize() throws Throwable { try { if (this.reportFD != null) { this.reportFD.close(); } } finally { super.finalize(); } } }