package hudson.plugins.seleniumhq; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.FilePath.FileCallable; import hudson.model.AbstractBuild; import hudson.model.AbstractItem; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.Project; import hudson.model.Result; import hudson.remoting.VirtualChannel; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import hudson.util.FormValidation; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.List; import net.sf.json.JSONObject; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.types.FileSet; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; /** * Clover {@link Publisher}. * * @author Pascal Martin */ public class SeleniumhqPublisher extends Recorder implements Serializable { /** * */ private static final long serialVersionUID = 1L; /** * {@link FileSet} "includes" string, like "foo/bar/*.html" */ private final String testResults; private final boolean useTestCommands; /** * * @param testResults * @param useTestCommands * @stapler-constructor */ @DataBoundConstructor public SeleniumhqPublisher(final String testResults, final boolean useTestCommands) { this.testResults = testResults; this.useTestCommands = useTestCommands; } public String getTestResults() { return testResults; } public Boolean getUseTestCommands() { return useTestCommands; } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } @Override public Action getProjectAction(AbstractProject<?,?> project) { return project instanceof Project ? new SeleniumhqProjectAction((Project)project) : null; } /** Gets the directory where the Clover Report is stored for the given project. */ public static File getSeleniumReportDir(AbstractItem project) { return new File(project.getRootDir(), "seleniumhq"); } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { listener.getLogger().println("Publishing Selenium report..."); SeleniumhqBuildAction action; // clear result directory FilePath rootTarget = new FilePath(getSeleniumReportDir(build.getParent())); rootTarget.deleteContents(); try { final long buildTime = build.getTimestamp().getTimeInMillis(); final long nowMaster = System.currentTimeMillis(); FilePath workspacePath = build.getWorkspace(); TestResult result = workspacePath.act(new FileCallable<TestResult>() { private static final long serialVersionUID = 1L; public TestResult invoke(File ws, VirtualChannel channel) throws IOException { final long nowSlave = System.currentTimeMillis(); FileSet fs = Util.createFileSet(ws, testResults); DirectoryScanner ds = fs.getDirectoryScanner(); String[] files = ds.getIncludedFiles(); if (files.length == 0) { // no test result. Most likely a configuration error or fatal // problem throw new AbortException("No Test Report Found"); } return new TestResult(buildTime + (nowSlave - nowMaster), ds); } }); if (result.getNumTestTotal() == 0) { throw new AbortException("Result does not have test"); } action = new SeleniumhqBuildAction(build, result, listener); // Store result file List<String> files = result.getFiles(); if (files.size() == 1) { FilePath source = new FilePath(build.getWorkspace() ,files.get(0)); source.copyTo(new FilePath(rootTarget, "index.html")); } else { String header = "<html><head><title>Selenium result</title></head><body><center><br/><h2>Selenium Test Result</h2><ul>"; String footer = "</ul></center></body></html>"; OutputStream output = rootTarget.child("index.html").write(); output.write(header.getBytes()); int index = 0; // Make test index file for (String file : files) { FilePath source = new FilePath(build.getWorkspace(), file); String dest = index + "/" + source.getName(); source.copyTo(new FilePath(rootTarget, dest)); String link = "<li><a href=\"" + dest + "\">" + source.getName() + "</a></li>"; output.write(link.getBytes()); ++index; } output.write(footer.getBytes()); output.close(); } } catch (IOException e) { listener.error("Failed to archive Selenium reports"); listener.error(e.getMessage()); build.setResult(Result.FAILURE); return true; } catch (AbortException e) { if (build.getResult() != Result.FAILURE) { listener.error(e.getMessage()); build.setResult(Result.FAILURE); } return true; } build.getActions().add(action); listener.getLogger().println(" Test failures: " + action.getResult().getNumTestFailures()); listener.getLogger().println(" Test totals : " + action.getResult().getNumTestTotal()); listener.getLogger().println("------------------------"); listener.getLogger().println(" Command Passes : " + action.getResult().numCommandPasses()); listener.getLogger().println(" Command Failures : " + action.getResult().numCommandFailures()); listener.getLogger().println(" Command Errors : " + action.getResult().numCommandErrors()); if (useTestCommands) { if (action.getResult().numCommandFailures() > 0) { build.setResult(Result.UNSTABLE); } if (action.getResult().numCommandErrors() > 0) { build.setResult(Result.FAILURE); } } else { if (action.getResult().getNumTestFailures() > 0) { build.setResult(Result.UNSTABLE); } } return true; } @Extension public static class DescriptorImpl extends BuildStepDescriptor<Publisher> { public DescriptorImpl() { super(SeleniumhqPublisher.class); } public String getDisplayName() { return "Publish Selenium Report"; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { req.bindParameters(this, "seleniumhq."); save(); return super.configure(req, formData); } /** * Performs on-the-fly validation on the file mask wildcard. */ public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException { return FilePath.validateFileMask(project.getSomeWorkspace(), value); } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return Project.class.isAssignableFrom(jobType); } /** Creates a new instance of {@link SeleniumhqPublisher} from a submitted form. */ @Override public SeleniumhqPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { SeleniumhqPublisher instance = req.bindParameters(SeleniumhqPublisher.class, "seleniumhq."); return instance; } @Override public String getHelpFile() { return "/plugin/seleniumhq/help-publisher.html"; } } }