// Copyright � 2002-2007 Canoo Engineering AG, Switzerland. package com.canoo.webtest.steps.control; import java.util.List; import java.util.ListIterator; import javax.xml.namespace.QName; import javax.xml.xpath.XPathException; import org.apache.log4j.Logger; import org.apache.tools.ant.Task; import com.canoo.webtest.engine.StepExecutionException; import com.canoo.webtest.engine.xpath.XPathHelper; /** * A RepeatStep accepts one or more nested step elements and executes * them as many times as defined in the count attribute. * <p/> * Before it actually starts the execution of contained steps, the contained * steps are "expanded", i.e. cloned so that each invocation has * a dedicated step object. This is required since the step object serves * also as history element for logging results and execution times. * <p/> * As soon as one of the nested steps fails, the RepeatStep fails as * well (simply by propagating the TestStepFailed exception). * <p/> * It also updates a property #{count} with the current number of the * current repetition which can be accessed as a dynamic property if desired. * * * @author Carsten Seibert * @webtest.step * category="Core" * name="repeat" * description="This step encapsulates one or more test steps that shall be repeated a certain number of times. * Any kind of step can be nested." */ public class RepeatStep extends MultipleExecutionStepContainer { private static final Logger LOG = Logger.getLogger(RepeatStep.class); private static final String DEFAULT_COUNTERNAME = "count"; private Integer fCount; private int fStartCount; private Integer fEndCount; private int fStep = 1; private String fCounterName = DEFAULT_COUNTERNAME; private String fXpath; /** * @param count * @webtest.parameter * required="yes/no" * description="The number of times that the included steps shall be executed. Specify either count or endCount. Counter values start at <em>startCount</em> and go up to <em>startCount + count - 1</em>." */ public void setCount(final Integer count) { fCount = count; } public Integer getCount() { return fCount; } /** * @param count * @webtest.parameter * required="no" * default="0" * description="The initial value of the count. Doesn't affect the number of iterations, just the value appearing in the counter." */ public void setStartCount(final int count) { fStartCount = count; } public int getStartCount() { return fStartCount; } /** * @param count * @webtest.parameter * required="yes/no" * description="The final value of the count. * Specify either count or endCount. Counter values start at <em>startCount</em> and go up to <em>endCount</em>." */ public void setEndCount(final Integer count) { fEndCount = count; } public Integer getEndCount() { return fEndCount; } /** * @param step * @webtest.parameter * required="no" * default="1" * description="The step size of the count. * Doesn't affect the number of iterations, just the value appearing in the counter." */ public void setStep(final int step) { fStep = step; } public int getStep() { return fStep; } /** * @param counterName * @webtest.parameter * required="no" * default="count" * description="The name that shall be used to reference the current repetition counter." */ public void setCounterName(final String counterName) { fCounterName = counterName; } public String getCounterName() { return fCounterName; } protected void verifyParameters() { super.verifyParameters(); if (getStep() < 1) { throw new StepExecutionException("Step must be greater than or equal to 1!", this); } if (getCount() != null) { if (getCount().intValue() < 0) { throw new StepExecutionException("Repeat count must be greater than or equal to 0!", this); } } else if (getEndCount() != null) { if (getEndCount().intValue() < fStartCount) { throw new StepExecutionException("endCount ("+fEndCount+") must be greater than or equal to startCount (" + fStartCount + ")!", this); } } else if (getXpath() == null) { throw new StepExecutionException("You must specify a count, a endCount or a XPath attribute.", this); } } public void doExecute() throws XPathException { if (getXpath() != null) doExecuteWithXPath(); else { final int first = getStartCount(); final int last; if (getCount() != null) last = first + getCount().intValue(); else last = getEndCount().intValue() + 1; for (int i=first; i<last; i+=getStep()) { setWebtestProperty(getCounterName(), Integer.toString(i)); executeContainedTasks(String.valueOf(i)); } } // TODO: generate report for not executed steps too } protected List getNodesByXPath() throws XPathException { return getContext().getXPathHelper().selectNodes(getContext().getCurrentResponse(), getXpath()); } protected void doExecuteWithXPath() throws XPathException { LOG.debug("repeat with xpath " + getXpath()); final XPathHelper xpathHelper = getContext().getXPathHelper(); final List nodes = getNodesByXPath(); final int nbNodes = nodes.size(); LOG.debug("Iterating over " + nbNodes + " nodes"); for (final ListIterator iter = nodes.listIterator(); iter.hasNext();) { final Object node = iter.next(); final String loopLabel = iter.nextIndex() + "/" + nbNodes; LOG.debug("Iteration " + loopLabel + ": placing current node >" + node + "< as >" + getCounterName() + "< in variable context"); xpathHelper.getVariableContext().setVariableValue(new QName(getCounterName()), node); executeContainedTasks(loopLabel); } } protected void executeContainedTasks(final String loopLabel) { LOG.debug("creating wrapper for current iteration (" + getCounterName() + "): " + loopLabel); final Task iterationWrapper = createIterationWrapper("Iteration " + loopLabel); LOG.debug("execution wrapper for current iteration (" + getCounterName() + "): " + loopLabel); iterationWrapper.perform(); } public String getXpath() { return fXpath; } /** * @webtest.parameter * required="yes/no" * description="Specifies the <key>XPATH</key> expression which evaluation gives the results on which to iterate" */ public void setXpath(final String xpath) { fXpath = xpath; } }