View Javadoc
1   package com.github.searls.jasmine.mojo;
2   
3   import com.github.klieber.phantomjs.locate.PhantomJsLocatorOptions;
4   import com.github.klieber.phantomjs.locate.RepositoryDetails;
5   import com.github.searls.jasmine.NullLog;
6   import com.github.searls.jasmine.driver.WebDriverFactory;
7   import com.github.searls.jasmine.format.JasmineResultLogger;
8   import com.github.searls.jasmine.io.RelativizesFilePaths;
9   import com.github.searls.jasmine.model.JasmineResult;
10  import com.github.searls.jasmine.runner.CreatesRunner;
11  import com.github.searls.jasmine.runner.ReporterType;
12  import com.github.searls.jasmine.runner.SpecRunnerExecutor;
13  import com.github.searls.jasmine.server.ResourceHandlerConfigurator;
14  import com.github.searls.jasmine.server.ServerManager;
15  import org.apache.maven.execution.MavenSession;
16  import org.apache.maven.plugin.MojoExecutionException;
17  import org.apache.maven.plugin.MojoFailureException;
18  import org.apache.maven.plugin.logging.Log;
19  import org.apache.maven.plugins.annotations.LifecyclePhase;
20  import org.apache.maven.plugins.annotations.Mojo;
21  import org.apache.maven.plugins.annotations.Parameter;
22  import org.apache.maven.plugins.annotations.ResolutionScope;
23  import org.eclipse.aether.RepositorySystem;
24  import org.eclipse.aether.RepositorySystemSession;
25  import org.eclipse.aether.repository.RemoteRepository;
26  import org.eclipse.jetty.server.Server;
27  import org.openqa.selenium.WebDriver;
28  
29  import javax.inject.Inject;
30  import java.io.File;
31  import java.net.URL;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Properties;
35  
36  /**
37   * Execute specs using Selenium Web Driver. Uses PhantomJsDriver for head-less execution by default.
38   */
39  @Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, requiresDependencyResolution = ResolutionScope.TEST)
40  public class TestMojo extends AbstractJasmineMojo {
41  
42    /**
43     * Determines the Selenium WebDriver class we'll use to execute the tests. See the Selenium documentation for more details.
44     * The plugin uses <a href="https://github.com/detro/ghostdriver">PhantomJSDriver</a> by default.
45     * <br>
46     * <p>Some valid examples:</p>
47     * <ul>
48     * <li>org.openqa.selenium.htmlunit.HtmlUnitDriver</li>
49     * <li>org.openqa.selenium.phantomjs.PhantomJSDriver</li>
50     * <li>org.openqa.selenium.firefox.FirefoxDriver</li>
51     * <li>org.openqa.selenium.ie.InternetExplorerDriver</li>
52     * </ul>
53     * <br>
54     * See the webDriverCapabilities property for configuring driver specific properties.
55     *
56     * @since 1.1.0
57     */
58    @Parameter(defaultValue = "org.openqa.selenium.phantomjs.PhantomJSDriver")
59    protected String webDriverClassName;
60  
61    /**
62     * <p>Web driver capabilities used to initialize a DesiredCapabilities instance when creating a web driver.</p>
63     * <br>
64     * <p>Capabilities value can be either a String, a List, or a Map.</p>
65     * <br>
66     * <p>Example:</p>
67     * <pre>
68     * &lt;webDriverCapabilities&gt;
69     *   &lt;capability&gt;
70     *     &lt;name&gt;phantomjs.binary.path&lt;/name&gt;
71     *     &lt;value&gt;/opt/phantomjs/bin/phantomjs&lt;/value&gt;
72     *   &lt;/capability&gt;
73     *   &lt;capability&gt;
74     *     &lt;name&gt;phantomjs.cli.args&lt;/name&gt;
75     *     &lt;list&gt;
76     *       &lt;value&gt;--disk-cache=true&lt;/value&gt;
77     *       &lt;value&gt;--max-disk-cache-size=256&lt;/value&gt;
78     *     &lt;/list&gt;
79     *   &lt;/capability&gt;
80     *   &lt;capability&gt;
81     *     &lt;name&gt;proxy&lt;/name&gt;
82     *     &lt;map&gt;
83     *       &lt;httpProxy&gt;myproxyserver.com:8000&lt;/httpProxy&gt;
84     *     &lt;/map&gt;
85     *   &lt;/capability&gt;
86     * &lt;/webDriverCapabilities&gt;
87     * </pre>
88     *
89     * @since 1.3.1.1
90     */
91    @Parameter
92    protected List<Capability> webDriverCapabilities = Collections.emptyList();
93  
94    /**
95     * <p>Determines the browser and version profile that HtmlUnit will simulate. This setting does nothing if the plugin is configured not to use HtmlUnit.
96     * This maps 1-to-1 with the public static instances found in {@link com.gargoylesoftware.htmlunit.BrowserVersion}.</p>
97     * <br>
98     * <p>Some valid examples: CHROME, FIREFOX_17, INTERNET_EXPLORER_9, INTERNET_EXPLORER_10</p>
99     *
100    * @since 1.1.0
101    * @deprecated Use the webDriverCapabilities parameter instead.
102    */
103   @Parameter(defaultValue = "FIREFOX_17")
104   @Deprecated
105   protected String browserVersion;
106 
107   /**
108    * <p>Determines the format that jasmine:test will print to console.</p>
109    * <p>Valid options:</p>
110    * <ul>
111    * <li>"documentation" - (default) - print specs in a nested format</li>
112    * <li>"progress" - more terse, with a period for a passed specs and an 'F' for failures (e.g. '...F...')</li>
113    * </ul>
114    *
115    * @since 1.1.0
116    */
117   @Parameter(defaultValue = "documentation")
118   protected String format;
119 
120   /**
121    * <p>Configure which version of PhantomJS should be used and how it should be found. The core of the
122    * <a href="http://klieber.github.io/phantomjs-maven-plugin">phantomjs-maven-plugin</a> is used to provide this
123    * functionality and this parameter should match the configuration of the
124    * <a href="http://kylelieber.com/phantomjs-maven-plugin/install-mojo.html">phantomjs-maven-plugin install</a> goal.</p>
125    * <br>
126    * <p>Default Options:</p>
127    * <pre>
128    * &lt;phantomjs&gt;
129    *   &lt;version&gt;2.0.0&lt;/version&gt;
130    *   &lt;checkSystemPath&gt;true&lt;/checkSystemPath&gt;
131    *   &lt;enforceVersion&gt;true&lt;/enforceVersion&gt;
132    *   &lt;source&gt;REPOSITORY&lt;/source&gt;
133    *   &lt;baseUrl&gt;&lt;/baseUrl&gt;
134    *   &lt;outputDirectory&gt;target/phantomjs&lt;/outputDirectory&gt;
135    * &lt;/phantomjs&gt;
136    * </pre>
137    *
138    * @since 2.0
139    */
140   @Parameter(property = "phantomjs", defaultValue = "${phantomJs}")
141   protected PhantomJsOptions phantomjs;
142 
143   /**
144    * Keep the server alive after the <code>jasmine:test</code> goal exists.
145    * Useful if you need to run further analysis on your tests, like collecting code coverage.
146    *
147    * @since 1.3.1.0
148    */
149   @Parameter(property = "keepServerAlive", defaultValue = "false")
150   protected boolean keepServerAlive;
151 
152   @Parameter(
153     defaultValue = "${repositorySystemSession}",
154     readonly = true
155   )
156   private RepositorySystemSession repositorySystemSession;
157 
158   @Parameter(
159     defaultValue = "${project.remoteProjectRepositories}",
160     readonly = true
161   )
162   private List<RemoteRepository> remoteRepositories;
163 
164   @Parameter(
165     defaultValue = "${session}",
166     readonly = true
167   )
168   private MavenSession mavenSession;
169 
170   private RepositorySystem repositorySystem;
171 
172   private final RelativizesFilePaths relativizesFilePaths;
173 
174   @Inject
175   public TestMojo(RepositorySystem repositorySystem) {
176     this.repositorySystem = repositorySystem;
177     this.relativizesFilePaths = new RelativizesFilePaths();
178   }
179 
180   @Override
181   public void execute() throws MojoExecutionException, MojoFailureException {
182     if (!this.isSkipTests()) {
183       super.execute();
184     } else {
185       this.getLog().info("Skipping Jasmine Specs");
186     }
187   }
188 
189   @Override
190   public void run() throws Exception {
191     ServerManager serverManager = this.getServerManager();
192     try {
193       int port = serverManager.start();
194       setPortProperty(port);
195       this.getLog().info("Executing Jasmine Specs");
196       JasmineResult result = this.executeSpecs(new URL(this.uriScheme + "://" + this.serverHostname + ":" + port));
197       this.logResults(result);
198       this.throwAnySpecFailures(result);
199     } finally {
200       if (!keepServerAlive) {
201         serverManager.stop();
202       }
203     }
204   }
205 
206   private ServerManager getServerManager() throws MojoExecutionException {
207     Log log = this.debug ? this.getLog() : new NullLog();
208 
209     CreatesRunner createsRunner = new CreatesRunner(
210       this,
211       log,
212       this.specRunnerHtmlFileName,
213       ReporterType.JsApiReporter);
214 
215     ResourceHandlerConfigurator configurator = new ResourceHandlerConfigurator(
216       this,
217       this.relativizesFilePaths,
218       createsRunner);
219 
220     return new ServerManager(new Server(), getConnector(), configurator);
221   }
222 
223   private void setPortProperty(int port) {
224     this.mavenProject.getProperties().setProperty("jasmine.serverPort", String.valueOf(port));
225   }
226 
227   private JasmineResult executeSpecs(URL runner) throws Exception {
228     WebDriver driver = this.createDriver();
229     JasmineResult result = new SpecRunnerExecutor().execute(
230       runner,
231       driver,
232       this.timeout,
233       this.debug,
234       this.getLog(),
235       this.format,
236       getReporters(),
237       getFileSystemReporters()
238     );
239     return result;
240   }
241 
242   private WebDriver createDriver() throws Exception {
243     RepositoryDetails details = new RepositoryDetails();
244     details.setRemoteRepositories(remoteRepositories);
245     details.setRepositorySystem(repositorySystem);
246     details.setRepositorySystemSession(repositorySystemSession);
247 
248     configure(mavenSession.getUserProperties());
249 
250     WebDriverFactory factory = new WebDriverFactory();
251     factory.setWebDriverCapabilities(webDriverCapabilities);
252     factory.setWebDriverClassName(webDriverClassName);
253     factory.setDebug(debug);
254     factory.setBrowserVersion(browserVersion);
255     factory.setPhantomJsLocatorOptions(phantomjs);
256     factory.setRepositoryDetails(details);
257 
258     return factory.createWebDriver();
259   }
260 
261   private void configure(Properties properties) {
262 
263     phantomjs.setVersion(
264       properties.getProperty("phantomjs.version", phantomjs.getVersion())
265     );
266 
267     phantomjs.setSource(
268       PhantomJsLocatorOptions.Source.valueOf(
269         properties.getProperty("phantomjs.source", phantomjs.getSource().toString())
270       )
271     );
272 
273     phantomjs.setOutputDirectory(
274       new File(properties.getProperty("phantomjs.outputDirectory", phantomjs.getOutputDirectory().toString()))
275     );
276 
277     phantomjs.setBaseUrl(
278       properties.getProperty("phantomjs.baseUrl", phantomjs.getBaseUrl())
279     );
280 
281     phantomjs.setCheckSystemPath(
282       configureBoolean(properties, "phantomjs.checkSystemPath", phantomjs.isCheckSystemPath())
283     );
284 
285     phantomjs.setEnforceVersion(
286       properties.getProperty("phantomjs.enforceVersion", phantomjs.getEnforceVersion())
287     );
288   }
289 
290   private boolean configureBoolean(Properties properties, String property, boolean defaultValue) {
291     return Boolean.parseBoolean(properties.getProperty(property, Boolean.toString(defaultValue)));
292   }
293 
294   private void logResults(JasmineResult result) {
295     JasmineResultLogger resultLogger = new JasmineResultLogger();
296     resultLogger.setLog(this.getLog());
297     resultLogger.log(result);
298   }
299 
300   private void throwAnySpecFailures(JasmineResult result) throws MojoFailureException {
301     if (this.haltOnFailure && !result.didPass()) {
302       throw new MojoFailureException("There were Jasmine spec failures.");
303     }
304   }
305 }