AbstractCasperJSRunnerMojo.java
package com.github.casperjs.casperjsrunner;
import static com.github.casperjs.casperjsrunner.CasperJsRuntimeFinder.findCasperRuntime;
import static com.github.casperjs.casperjsrunner.CasperJsVersionRetriever.retrieveVersion;
import static com.github.casperjs.casperjsrunner.LogUtils.getLogger;
import static com.github.casperjs.casperjsrunner.PathToNameBuilder.buildName;
import static com.github.casperjs.casperjsrunner.PatternsChecker.checkPatterns;
import static com.github.casperjs.casperjsrunner.cmd.CommandExecutor.executeCommand;
import static com.github.casperjs.casperjsrunner.cmd.CommandLineComputer.computeCmdLine;
import static com.github.casperjs.casperjsrunner.cmd.ParametersFactory.buildParameters;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.toolchain.ToolchainManager;
import com.github.casperjs.casperjsrunner.cmd.CasperJsRuntime;
import com.github.casperjs.casperjsrunner.cmd.NativeOptions;
import com.github.casperjs.casperjsrunner.cmd.Reports;
import com.github.casperjs.casperjsrunner.cmd.ScriptOptions;
import com.github.casperjs.casperjsrunner.cmd.TestOptions;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Runs JavaScript and/or CoffeScript test files on CasperJS instance
*
* @author Romain Linsolas
* @author Benoit Guerin
* @since 1.0.0
*/
public abstract class AbstractCasperJSRunnerMojo extends AbstractMojo {
// Parameters for the plugin
/**
* Complete path of the executable for CasperJS. <br/>
* <b>Default value:</b> Found from <a href="http://maven.apache.org/guides/mini/guide-using-toolchains.html">toolchain</a> named
* <b><i>casperjs</b></i>, then from this parameter, then from PATH with default value of <b>casperjs</b> on Linux/Mac or <b>casperjs.exe</b>
* (<b>casperjs.bat</b> for compatibility with old versions of CasperJS) on Windows
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.executable")
private String casperExecPath;
/**
* Directory where the tests to execute are stored. <br/>
* If <code>${tests.directory}/includes</code> and <code>${tests.directory}/scripts</code> directories exist, this is changed to
* <code>${tests.directory}/scripts</code> and all <code>*.js</code> files in <code>${tests.directory}/includes</code> will automatically be added
* to the CasperJS <code>--includes</code> list.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.tests.directory", defaultValue = "${basedir}/src/test/casperjs")
private File testsDir;
/**
* Specify this parameter to run individual tests by file name, overriding the <code>testIncludes</code>/<code>testExcludes</code> parameters.
* Each pattern you specify here will be used to create an include pattern formatted like <code>**/${test}.{js,coffee}</code>, so you can just
* type "-Dtest=MyTest" to run a single test called <code>foo/MyTest.js</code> or <code>foo/MyTest.coffee</code>.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.test")
private String test;
/**
* A list of <code><testsInclude></code> elements specifying the tests (by pattern) that should be included in testing. <br/>
* <b>Default value:</b> When not specified and when the test parameter is not specified, the default includes will be (javascript patterns will
* only be set if <code>includeJS</code> is <code>true</code>, and coffee patterns will only be set if <code>includeCS</code> is <code>true</code>
* ) <br/>
* <br/>
* <code><testsIncludes><br/>
<testsInclude>**/Test*.js</testsInclude><br/>
<testsInclude>**/*Test.js</testsInclude><br/>
<testsInclude>**/*TestCase.js</testsInclude><br/>
<testsInclude>**/Test*.coffee</testsInclude><br/>
<testsInclude>**/*Test.coffee</testsInclude><br/>
<testsInclude>**/*TestCase.coffee</testsInclude><br/>
</testsIncludes></code>
*
* @since 1.0.1
*/
@Parameter
private List<String> testsIncludes;
/**
* A list of <code><testsExclude></code> elements specifying the tests (by pattern) that should be excluded in testing.
*
* @since 1.0.1
*/
@Parameter
private List<String> testsExcludes;
/**
* Set the plugin to be verbose during its execution.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.verbose", defaultValue = "${maven.verbose}")
private boolean verbose = false;
/**
* A flag to indicate if the *.js found in <code>tests.directory</code> should be executed.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.include.javascript", defaultValue = "true")
private boolean includeJS;
/**
* A flag to indicate if the *.coffee found in <code>tests.directory</code> should be executed.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.include.coffeescript", defaultValue = "true")
private boolean includeCS;
/**
* Environment variables to set on the command line, instead of the default, inherited, ones.
*
* @since 1.0.0
*/
@Parameter
private Map<String, String> environmentVariables;
/**
* Set this to <code>true</code> to bypass unit tests entirely.
*
* @since 1.0.1
*/
@Parameter(property = "casperjs.skip", defaultValue = "${maven.test.skip}")
private boolean skip = false;
// Parameters for the CasperJS options
/**
* Set the value for the CasperJS option <code>--pre=[pre-test.js]</code>: will add the tests contained in pre-test.js before executing the test
* suite. If a <code>pre.js</code> file is found on the <code>${tests.directory}</code>, this option will be set automatically
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.pre")
private String pre;
/**
* Set the value for the CasperJS option <code>--post=[post-test.js]</code>: will add the tests contained in post-test.js after having executed
* the whole test suite. If a <code>post.js</code> file is found on the <code>${tests.directory}</code>, this option will be set automatically
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.post")
private String post;
/**
* Set the value for the CasperJS option <code>--includes=[foo.js,bar.js]</code>: will includes the foo.js and bar.js files before each test file
* execution.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.includes")
private String includes;
/**
* A list of <code><includesPattern></code> elements specifying the files (by pattern) to set on the <code>--includes</code> option.<br/>
* When not specified and the <code>${tests.directory}/includes</code> directory exists, this will be set to <br/>
* <br/>
* <code><includesPatterns><br/>
<includesPattern>${tests.directory}/includes/**/*.js</includesPattern><br/>
</includesPatterns></code>
*
* @since 1.0.1
*/
@Parameter
private List<String> includesPatterns;
/**
* Should CasperJS generates log reports. If <code>true</code> (which is the default), a log file will be generated for each test, containing the
* output of the CasperJS run. These reports will be generated in the
* <code>reportsDirectory<code> directory, with a name of <code>test filename>.txt</code>.
*
* @since 1.0.5
*/
@Parameter(property = "casperjs.enableLogReports", defaultValue = "true")
private boolean enableLogReports;
/**
* Should CasperJS generates XML reports, through the <code>--xunit=[filename]</code> option. If <code>true</code>, such reports will be generated
* in the <code>reportsDirectory<code> directory,
* with a name of <code>TEST-<test filename>.xml</code>.
*
* @since 1.0.2
*/
@Parameter(property = "casperjs.enableXmlReports", defaultValue = "false")
private boolean enableXmlReports;
/**
* Directory where the xUnit reports will be stored.
*
* @since 1.0.2
*/
@Parameter(property = "casperjs.reports.directory", defaultValue = "${project.build.directory}/casperjs-reports")
private File reportsDir;
/**
* Set the value for the CasperJS option <code>--log-level=[logLevel]</code>: sets the logging level (see http://casperjs.org/logging.html).
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.logLevel")
private String logLevel;
/**
* Set the CasperJS --direct option: will output log messages directly to the console. Deprecated: use the <code>casperjsVerbose</code> option
*
* @since 1.0.0
* @deprecated
*/
@Deprecated
@Parameter(property = "casperjs.direct", defaultValue = "false")
private boolean direct;
/**
* For CasperJS 1.1.x, set --verbose option, --direct for CasperJS 1.0.x: will output log messages directly to the console
*
* @since 1.0.2
*/
@Parameter(property = "casperjs.casperjsVerbose", defaultValue = "false")
private boolean casperjsVerbose;
/**
* Set the CasperJS --fail-fast option: will terminate the current test suite as soon as a first failure is encountered.
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.failFast", defaultValue = "false")
private boolean failFast;
/**
* CasperJS 1.1 and above<br/>
* Set the for the CasperJS option <code>--engine=[engine]</code>: will change the rendering engine (phantomjs or slimerjs)
*
* @since 1.0.0
*/
@Parameter(property = "casperjs.engine")
private String engine;
/**
* A list of <code><argument></code> to add to the casperjs command line.
*
* @since 1.0.0
*/
@Parameter
private List<String> arguments;
// Injected components
/**
* The directory where output files will be stored
*
* @since 1.0.0
*/
@Parameter(defaultValue = "${project.build.directory}/casperjs")
private File targetDir;
/**
* The current maven session, used by the ToolChainManager
*
* @since 1.0.0
*/
@Parameter(defaultValue = "${session}")
private MavenSession session;
/**
* ToolChainManager, used to retrieve the CasperJS runtime path from user's configured toolchains
*/
@Component
private ToolchainManager toolchainManager;
/**
* The CasperJS runtime path that we will launch
*/
private String casperRuntime;
/**
* The CasperJS runtime version
*/
private ArtifactVersion casperJsVersion;
/**
* The directory containing the scripts to include while launching tests
*/
private File includesDir;
/**
* The directory containing the tests to launch
*/
private File scriptsDir;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
LogUtils.setLog(getLog(), verbose);
if (skip) {
getLogger().info("Skipping CasperJsRunner execution");
return;
}
init();
final Collection<String> scripts = findScripts();
final Result globalResult = executeScripts(scripts);
getLogger().info(globalResult.print());
afterTestExecution(globalResult);
}
protected void afterTestExecution(final Result globalResult) throws MojoFailureException, MojoExecutionException {
}
private void init() throws MojoFailureException {
casperRuntime = findCasperRuntime(toolchainManager, session, casperExecPath);
if (StringUtils.isBlank(casperRuntime)) {
throw new MojoFailureException("CasperJS executable not found");
}
casperJsVersion = retrieveVersion(casperRuntime, verbose);
if (verbose) {
getLogger().info("CasperJS version: " + casperJsVersion);
}
if (direct && (casperJsVersion.getMajorVersion() > 1 || casperJsVersion.getMajorVersion() == 1 && casperJsVersion.getMinorVersion() > 0)) {
getLogger().warn("direct option is deprecated, use casperjsVerbose instead");
casperjsVerbose = true;
}
initIncludesAndExcludes();
initDefaultDirectories();
initReportsDirectory();
}
private void initIncludesAndExcludes() {
testsIncludes = checkPatterns(testsIncludes, includeJS, includeCS);
if (testsExcludes == null) {
testsExcludes = new ArrayList<String>();
}
if (includesPatterns == null) {
includesPatterns = new ArrayList<String>();
}
}
private void initDefaultDirectories() {
includesDir = testsDir;
scriptsDir = testsDir;
final File defaultIncludesDir = new File(testsDir, "includes");
final File defaultScriptsDir = new File(testsDir, "scripts");
if (defaultScriptsDir.exists() && defaultScriptsDir.isDirectory()) {
getLogger().debug("'scripts' subdirectory found, altering 'scriptsDir'");
scriptsDir = defaultScriptsDir;
if (defaultIncludesDir.exists() && defaultIncludesDir.isDirectory() && includesPatterns.isEmpty()) {
getLogger().debug("'includes' subdirectory found and 'includesPatterns' is empty, altering 'includesDir' and 'includesPatterns'");
includesDir = defaultIncludesDir;
includesPatterns.add("**/*.js");
}
}
}
private void initReportsDirectory() {
if (enableXmlReports || enableLogReports) {
getLogger().debug("creating directories to hold log/xunit file(s)");
reportsDir.mkdirs();
}
}
private Collection<String> findScripts() {
return new OrdererScriptsFinderDecorator(new DefaultScriptsFinder(scriptsDir, test, testsIncludes, testsExcludes)).findScripts();
}
private Result executeScripts(final Collection<String> files) {
final Result result = new Result();
for (final String file : files) {
final File f = new File(scriptsDir, file);
getLogger().debug("Execution of test " + f.getName());
final int res = executeScript(f);
if (res == 0) {
result.addSuccess();
} else {
getLogger().warn("Test '" + f.getName() + "' has failure");
result.addFailure();
}
}
return result;
}
private int executeScript(final File f) {
final String scriptName = buildName(scriptsDir, f);
//@formatter:off
return executeCommand(
computeCmdLine(
buildParameters(
new CasperJsRuntime(casperRuntime, casperJsVersion),
new NativeOptions(failFast, casperjsVerbose, logLevel),
new TestOptions(engine, includes, includesPatterns, includesDir, pre, post, testsDir),
new Reports(enableXmlReports, reportsDir),
new ScriptOptions(scriptName, f, arguments))),
environmentVariables, verbose, computeLogFile(scriptName));
//@formatter:on
}
private File computeLogFile(final String scriptName) {
return enableLogReports ? new File(reportsDir, scriptName + ".txt") : null;
}
}