package hudson.plugins.seleniumhq; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.BuildListener; import hudson.model.Descriptor; import hudson.model.Result; import hudson.tasks.Builder; import hudson.util.FormValidation; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import net.sf.json.JSONObject; import org.apache.commons.io.FileUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; /** * Sample {@link Builder}. * * <p> * When the user configures the project and enables this builder, * {@link DescriptorImpl#newInstance(StaplerRequest)} is invoked and a new {@link SeleniumhqBuilder} * is created. The created instance is persisted to the project configuration XML by using XStream, * so this allows you to use instance fields (like {@link #name}) to remember the configuration. * * <p> * When a build is performed, the {@link #perform(Build, Launcher, BuildListener)} method will be * invoked. * * @author Pascal Martin */ public class SeleniumhqBuilder extends Builder { private final String browser; private final String startURL; private final String suiteFile; private final String resultFile; private final String other; @DataBoundConstructor public SeleniumhqBuilder(String browser, String startURL, String suiteFile, String resultFile, String other) { this.browser = browser; this.startURL = startURL; this.suiteFile = suiteFile; this.resultFile = resultFile; this.other = other; } /** * We'll use this from the <tt>config.jelly</tt>. */ public String getBrowser() { return browser; } /** * We'll use this from the <tt>config.jelly</tt>. */ public String getStartURL() { return startURL; } /** * We'll use this from the <tt>config.jelly</tt>. */ public String getSuiteFile() { return suiteFile; } /** * We'll use this from the <tt>config.jelly</tt>. */ public String getOther() { return other; } /** * We'll use this from the <tt>config.jelly</tt>. */ public String getResultFile() { return resultFile; } /** * Check if the suiteFile is a URL * * @return true if the suiteFile is a valid url else return false */ public boolean isURLSuiteFile() { try { URL url = new URL(this.suiteFile); return url != null; } catch (Exception e) { return false; } } /** * Check if the suiteFile is a file * * @return true if the suiteFile is a filePath else return false * @throws InterruptedException * @throws IOException */ public boolean isFileSuiteFile(AbstractBuild<?,?> build, Launcher launcher) throws IOException, InterruptedException { FilePath suiteFilePath = new FilePath(build.getWorkspace(), this.suiteFile); if (suiteFilePath.exists()) { return suiteFilePath.isDirectory() == false; } else { suiteFilePath = new FilePath(launcher.getChannel(), this.suiteFile); if (suiteFilePath.exists()) { return suiteFilePath.isDirectory() == false; } } return false; } @Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { // ------------------------------- // Check global config // ------------------------------- if (!DESCRIPTOR.isGoodSeleniumRunner()) { listener.error("Please configure the Selenium Remote Control htmlSuite Runner in admin of hudson"); build.setResult(Result.FAILURE); return false; } // ------------------------------- // Check projet config // ------------------------------- if (this.getBrowser() == null || this.getBrowser().length() == 0) { listener.error("Build config : browser field is mandatory"); build.setResult(Result.FAILURE); return false; } if (this.getStartURL() == null || this.getStartURL().length() == 0) { listener.error("Build config : startURL field is mandatory"); build.setResult(Result.FAILURE); return false; } if (this.getSuiteFile() == null || this.getSuiteFile().length() == 0) { listener.error("Build config : suiteFile field is mandatory"); build.setResult(Result.FAILURE); return false; } if (this.getResultFile() == null || this.getResultFile().length() == 0) { listener.error("Build config : resultFile field is mandatory"); build.setResult(Result.FAILURE); return false; } // ------------------------------- // check suiteFile type url or file // ------------------------------- String suiteFile = null; FilePath tempSuite = null; if (this.isFileSuiteFile(build, launcher)) { FilePath suiteFilePath = new FilePath(build.getWorkspace(), this.suiteFile); if (suiteFilePath.exists() == false) // File exist on remote { suiteFilePath = new FilePath(launcher.getChannel(), this.suiteFile); } suiteFile = suiteFilePath.getRemote(); } else if (this.isURLSuiteFile()) { tempSuite = build.getWorkspace().createTempFile("tempHtmlSuite", "html"); suiteFile = tempSuite.getRemote(); try { File localWorkspace = new File(build.getRootDir(), "workspace"); File tempSuiteLocal = localWorkspace.createTempFile("tempHtmlSuite", "html"); listener.getLogger().println("Try downloading suite file on master"); listener.getLogger().println(" from url : " + this.suiteFile ); listener.getLogger().println(" to file : " + tempSuiteLocal.getPath()); FileUtils.copyURLToFile(new URL(this.suiteFile), tempSuiteLocal); listener.getLogger().println(" ..."); listener.getLogger().println(" Succeed"); listener.getLogger().println("Try transfer suite file on slave"); listener.getLogger().println(" from file : " + tempSuiteLocal.getPath() ); listener.getLogger().println(" to file : " + suiteFile); FilePath sourceFile = new FilePath(tempSuiteLocal); sourceFile.copyTo(tempSuite); listener.getLogger().println(" ..."); listener.getLogger().println(" Succeed"); sourceFile.delete(); } catch(Exception e) { listener.error("Downloading suite file from url failed ! Check your build configuration. "); build.setResult(Result.FAILURE); return false; } } else { // The suiteFile it is a unsuported type listener.error("The suiteFile is not a file or an url ! Check your build configuration."); build.setResult(Result.FAILURE); return false; } // ------------------------------- // launch : java -jar selenium-server.jar [other] -htmlSuite "{browser}" "{startURL}" // "{suiteFile}" "{resultFile}" // ------------------------------- String seleniumRunner = FileUtil.getExecutableAbsolutePath(DESCRIPTOR.getSeleniumRunner()); FilePath resultFilePath = new FilePath(build.getWorkspace(), this.resultFile); resultFilePath.getParent().mkdirs(); String resultFile = resultFilePath.getRemote(); ArrayList cmd = new ArrayList(); cmd.add("java"); cmd.add("-jar"); cmd.add(seleniumRunner); cmd.addAll(this.getOthers()); cmd.add("-htmlSuite"); cmd.add(this.getBrowser()); cmd.add(this.getStartURL()); cmd.add(suiteFile); cmd.add(resultFile); try { String javaCmdString = ""; Iterator<String> itr = cmd.iterator(); while(itr.hasNext()) { javaCmdString += " " + itr.next(); } listener.getLogger().println(javaCmdString); launcher.launch().cmds(cmd).envs(build.getEnvironment(listener)).stdout(listener.getLogger()).pwd(build.getWorkspace()).join(); return true; } catch (IOException e) { e.printStackTrace(); listener.getLogger().println("IOException!"); return false; } catch (InterruptedException e) { e.printStackTrace(); listener.getLogger().println("InterruptedException!"); return false; } finally { // ------------------------------- // Delete the temp suite file // ------------------------------- if (tempSuite != null) tempSuite.delete(); } } /** * @desc get the arrayList of optional parameters * * @return ArrayList containing parameters */ private final ArrayList getOthers() { ArrayList cmdParams = new ArrayList(); if (this.getOther() != null && ! this.getOther().isEmpty()) { String otherParams = this.getOther(); String[] otherParamsArray = otherParams.split(" "); for (int i = 0; i < otherParamsArray.length; ++i) { // Leave spaces between quotes if (otherParamsArray[i].matches("\"")) { String paramBuffer = otherParamsArray[i]; do { ++i; paramBuffer += otherParamsArray[i]; } while ((!otherParamsArray[i].matches("\"")) && (i < otherParamsArray.length)); cmdParams.add(paramBuffer); } else if(otherParamsArray[i].matches("\'")) { String paramBuffer = otherParamsArray[i]; do { ++i; paramBuffer += otherParamsArray[i]; } while ((!otherParamsArray[i].matches("\'")) && (i < otherParamsArray.length)); cmdParams.add(paramBuffer); } else { cmdParams.add(otherParamsArray[i]); } } } return cmdParams; } @Extension public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); /** * Descriptor for {@link SeleniumhqBuilder}. Used as a singleton. The class is marked as public * so that it can be accessed from views. * * <p> * See <tt>views/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt> for the actual HTML * fragment for the configuration screen. */ public static final class DescriptorImpl extends Descriptor<Builder> { /** * To persist global configuration information, simply store it in a field and call save(). * * <p> * If you don't want fields to be persisted, use <tt>transient</tt>. */ private String seleniumRunner; DescriptorImpl() { super(SeleniumhqBuilder.class); load(); } /** * Performs on-the-fly validation of the form field 'name'. * * @param value * This receives the current value of the field. */ public FormValidation doCheckSeleniumRunner(@QueryParameter final String value) { return FormValidation.validateExecutable(value); } /** * This human readable name is used in the configuration screen. */ public String getDisplayName() { return "SeleniumHQ htmlSuite Run"; } @Override public boolean configure(StaplerRequest req, JSONObject o) throws FormException { // to persist global configuration information, // set that to properties and call save(). seleniumRunner = o.getString("seleniumRunner"); save(); return super.configure(req, o); } public String getSeleniumRunner() { return seleniumRunner; } /** * For junit test * * @param seleniumRunner */ public void setSeleniumRunner(String seleniumRunner) { this.seleniumRunner = seleniumRunner; } public boolean isGoodSeleniumRunner() { return this.seleniumRunner != null && this.seleniumRunner.length() > 0; } } }