// Copyright � 2004-2005 ASERT. Released under the Canoo Webtest license. package com.canoo.webtest.extension; import java.io.File; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import com.canoo.webtest.boundary.ResetScriptRunner; import com.canoo.webtest.engine.Context; import com.canoo.webtest.engine.StepExecutionException; import com.canoo.webtest.engine.StepFailedException; import com.canoo.webtest.steps.Step; import com.canoo.webtest.util.ConversionUtil; import com.canoo.webtest.util.FileUtil; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.xml.XmlPage; /** * Wrapper class for the ant script command.<p> * * @author Paul King * @webtest.step * category="Extension" * name="scriptStep" * description="Provides the ability to use scripting code in your tests." */ public class ScriptStep extends Step { private static final Logger LOG = Logger.getLogger(ScriptStep.class); private String fLanguage; private String fKeep; private File fSrc; private String fScriptText = ""; /** * Perform the step's actual work. * * @throws Exception if a problem occurred */ public void doExecute() throws Exception { final ResetScriptRunner runner; // delegate pattern final Context context = getContext(); if (context.getRunner() == null) { runner = new ResetScriptRunner(); runner.setLanguage(getLanguage()); context.setRunner(runner); LOG.debug("Creating new Script Runner with language: " + fLanguage); } else { runner = context.getRunner(); runner.reset(); } buildScript(); getProject().addReference("step", this); if (context.getCurrentResponse() == null) { LOG.warn("No response found. Previous invoke missing? Related scripting variables not created"); } else { setupResponseScriptingVariables(context); } try { executeByRunner(runner, getProject().replaceProperties(fScriptText), this, getProject()); // languages like groovy can throw assert errors which are useful to fail test // but what about an assert in groovy's implementation that has failed - this should // probably be configurable to potentially throw StepExecutionError } catch (AssertionError ae) { final String msg = "Assertion error during scriptStep: " + ae.getMessage(); LOG.debug(msg, ae); throw new StepFailedException(msg, this); } catch (BuildException be) { LOG.debug(be.getMessage(), be); throw new StepExecutionException("Error invoking script: " + be.getMessage(), this); } finally { if (!isKeep()) { context.setRunner(null); } } } public static String evalScriptExpression(final Context context, final String expression, final Step step) { final ResetScriptRunner runner = context.getRunner(); if (runner == null) { throw new StepExecutionException("Can't evaluate script property because no previous <scriptStep> with keep=true.", step); } runner.reset(); try { return evalByRunner(runner, expression, step); } catch (BuildException be) { throw new StepExecutionException("Error invoking script: " + be.getMessage(), step); } } public static void executeByRunner(final ResetScriptRunner runner, final String script, final Step step, final Project project) throws BuildException { runner.addText(script); runner.addBeans(project.getProperties()); runner.addBeans(project.getUserProperties()); runner.addBeans(project.getTargets()); runner.addBeans(project.getReferences()); runner.addBean("project", project); runner.addBean("self", step); runner.executeScript("WebTest"); } public static String evalByRunner(final ResetScriptRunner runner, final String script, final Step step) throws BuildException { runner.addText(script); runner.addBean("self", step); return runner.evalScript("WebTest"); } private void buildScript() { if (fSrc != null) { fScriptText += FileUtil.readFileToString(fSrc, this); } } private void setupResponseScriptingVariables(final Context context) { getProject().addReference("response", context.getCurrentResponse().getWebResponse()); if (context.getCurrentResponse() instanceof HtmlPage) { getProject().addReference("document", ((HtmlPage) context.getCurrentResponse()).getDocumentElement()); } else if (context.getCurrentResponse() instanceof XmlPage) { getProject().addReference("document", ((XmlPage) context.getCurrentResponse()).getXmlDocument()); } } /** * Verify that language is set * * @throws StepExecutionException if a mandatory attribute is not set */ protected void verifyParameters() { super.verifyParameters(); final ResetScriptRunner runner = getContext().getRunner(); if (runner == null) { emptyParamCheck(fLanguage, "language"); } else { paramCheck(fLanguage != null && !fLanguage.equals(runner.getLanguage()), "You may not change 'language' to '" + fLanguage + "' after previously using the 'keep' attribute (was: " + runner.getLanguage() + ")"); } paramCheck(fSrc == null && StringUtils.isEmpty(fScriptText), "Either \"src\" attribute or nested script text must be given."); } /** * override to add actual attribute values to the reporting */ protected void addComputedParameters(final Map map) { map.put("language", fLanguage); // cannot be null (mandatory) if (fSrc == null) { map.put("script", fScriptText); } else { map.put("src", fSrc); // (optional) } } /** * Defines the language (required). * * @param language Sets the scripting language. * @webtest.parameter * required="yes/no" * description="The scripting language to use. Required unless using the <em>keep</em> attribute in which case the value is optional but must agree with the original language if used. The value can be any language supported by the <key>BSF</key>, e.g. javascript, jacl, netrexx, java, javaclass, bml, vbscript, jscript, perlscript, perl, jpython, jython, lotusscript, xslt, pnuts, beanbasic, beanshell, ruby, judoscript, groovy." */ public void setLanguage(final String language) { fLanguage = language; } public String getLanguage() { return fLanguage; } /** * Defines the src file containing scripting code (optional). * * @param fileName Sets the name of the file containing script code. * @webtest.parameter * required="yes/no" * description="The name of the file containing the scripting code. You may omit this parameter if you have embedded script code." */ public void setSrc(final File fileName) { fSrc = fileName; } public File getSrc() { return fSrc; } /** * Flag to indicate that the scripting engine should be kept after the step completes and re-used for future script steps. * * @param keep Indicates that the script engine should be kept for future steps. * @webtest.parameter * required="no" * default="false" * description="Indicates that the script engine should be kept for future steps. Variables created during one script step will remain available." */ public void setKeep(final String keep) { fKeep = keep; } public String getKeep() { return fKeep; } public boolean isKeep() { return ConversionUtil.convertToBoolean(fKeep, false); } /** * The script text if inlined. * * @param text The nested scripting code. * @webtest.nested.parameter * required="yes/no" * description="The nested script code. You may omit this if you use the parameter src." */ public void addText(final String text) { fScriptText += text; } }