001: // Copyright © 2002-2005 Canoo Engineering AG, Switzerland.
002: package com.canoo.webtest.steps.form;
003:
004: import java.io.IOException;
005: import java.util.Iterator;
006: import java.util.List;
007:
008: import org.apache.commons.lang.StringUtils;
009: import org.apache.log4j.Logger;
010: import org.xml.sax.SAXException;
011:
012: import com.canoo.webtest.engine.IStringVerifier;
013: import com.canoo.webtest.engine.StepFailedException;
014: import com.canoo.webtest.extension.StoreElementAttribute;
015: import com.canoo.webtest.steps.AbstractBrowserAction;
016: import com.canoo.webtest.steps.Step;
017: import com.canoo.webtest.util.ConversionUtil;
018: import com.gargoylesoftware.htmlunit.html.HtmlElement;
019: import com.gargoylesoftware.htmlunit.html.HtmlForm;
020: import com.gargoylesoftware.htmlunit.html.HtmlLabel;
021: import com.gargoylesoftware.htmlunit.html.HtmlPage;
022:
023: /**
024: * Abstract class for steps which update form fields.
025: * This class handles the attributes name/formName, htmlId or xpath.
026: *
027: * @author Marc Guillemot
028: * @author Paul King
029: * @author Denis N. Antonioli
030: */
031: public abstract class AbstractSetFieldStep extends
032: AbstractBrowserAction {
033: private static final Logger LOG = Logger
034: .getLogger(AbstractSetFieldStep.class);
035:
036: public static final String MESSAGE_ARGUMENT_MISSING = "One of 'forLabel', 'htmlId', 'name', or 'xpath' must be set!";
037: public static final String MESSAGE_ARGUMENT_REDUNDANT = "Only one of 'forLabel', 'htmlId', 'name', and 'xpath' should be set!";
038:
039: private String fName;
040: private String fXPath;
041: private String fFormName;
042: private String fFieldIndex;
043: private String fHtmlId;
044: private String fForLabel;
045:
046: /**
047: * Set the name.
048: * @param name
049: * @webtest.parameter required="yes/no"
050: * description="The name of the input field of interest.
051: * One of <em>forLabel</em>, <em>htmlId</em>, <em>name</em>, or <em>xpath</em> is required."
052: */
053: public void setName(final String name) {
054: fName = name;
055: }
056:
057: public String getName() {
058: return fName;
059: }
060:
061: /**
062: * Set the text of the label associated with the field to set.
063: * @param text the label text
064: * @webtest.parameter required="yes/no"
065: * description="The text of the label field associated with the input field of interest.
066: * One of <em>forLabel</em>, <em>htmlId</em>, <em>name</em>, or <em>xpath</em> is required."
067: */
068: public void setForLabel(final String text) {
069: fForLabel = text;
070: }
071:
072: public String getForLabel() {
073: return fForLabel;
074: }
075:
076: public String getXpath() {
077: return fXPath;
078: }
079:
080: /**
081: * Set the xpath.
082: *
083: * @param xpath
084: * @webtest.parameter required="yes/no"
085: * description="The xpath of the input field of interest.
086: * One of <em>forLabel</em>, <em>htmlId</em>, <em>name</em>, or <em>xpath</em> is required."
087: */
088: public void setXpath(final String xpath) {
089: fXPath = xpath;
090: }
091:
092: /**
093: * Set the form name.
094: * @param formName
095: * @webtest.parameter required="no"
096: * default="the last form selected using 'selectForm', otherwise searches all forms"
097: * description="The name of the form containing the field of interest. Ignored if <em>htmlId</em> is used."
098: */
099: public void setFormName(final String formName) {
100: fFormName = formName;
101: }
102:
103: public String getFormName() {
104: return fFormName;
105: }
106:
107: public String getHtmlId() {
108: return fHtmlId;
109: }
110:
111: /**
112: * Set the html id.
113: *
114: * @param htmlId
115: * @webtest.parameter required="yes/no"
116: * description="The id of the input field of interest.
117: * One of <em>forLabel</em>, <em>htmlId</em>, <em>name</em>, or <em>xpath</em> is required."
118: */
119: public void setHtmlId(final String htmlId) {
120: fHtmlId = htmlId;
121: }
122:
123: /**
124: * Set the index.
125: *
126: * @param index
127: * @webtest.parameter required="no"
128: * default="the first field found that matches criteria"
129: * description="The index of the field of interest (starting at 0) if more than one field matches criteria. Ignored if <em>htmlId</em> or <em>xpath</em> is used."
130: */
131: public void setFieldIndex(final String index) {
132: fFieldIndex = index;
133: }
134:
135: public String getFieldIndex() {
136: return fFieldIndex;
137: }
138:
139: public void doExecute() throws SAXException, IOException {
140: if (fName != null) {
141: final HtmlForm form = findForm();
142: if (form == null) {
143: throw new StepFailedException(
144: "No suitable form found having field named \""
145: + getName() + "\"", this );
146: }
147: LOG.debug("Found matching form " + form);
148: setField(selectField(trimFields(findFields(form)),
149: getFieldIndex(), this ));
150: } else if (getForLabel() != null) {
151: setField(findFieldByLabel(getContext()
152: .getCurrentHtmlResponse(this ), getForLabel()));
153: } else { // htmlId, xpath
154: setField(StoreElementAttribute.findElement(getContext()
155: .getCurrentHtmlResponse(this ), getHtmlId(),
156: getXpath(), LOG, this ));
157: }
158: }
159:
160: /**
161: * Retrieves the (first) field associated with the label containing the provided text
162: * @param page the page to search in
163: * @param labelText the text of the label
164: * @return the associated form field
165: * @throws StepFailedException if no field is found
166: */
167: HtmlElement findFieldByLabel(final HtmlPage page,
168: final String labelText) {
169: LOG.debug("Searching label tag with text: " + labelText);
170: final List labels = page.getDocumentHtmlElement()
171: .getHtmlElementsByTagName("label");
172: LOG.debug(labels.size() + " found in the page");
173:
174: final IStringVerifier verifier = getVerifier(false);
175: for (final Iterator iter = labels.iterator(); iter.hasNext();) {
176: final HtmlLabel label = (HtmlLabel) iter.next();
177: if (verifier.verifyStrings(labelText, label.asText())) {
178: LOG
179: .debug("Found label with matching text, examining the associated field: "
180: + label);
181: final HtmlElement target = label.getReferencedElement();
182: if (keepField(target)) {
183: LOG.debug("Found field: " + target);
184: return target;
185: } else {
186: LOG.debug("Target doesn't match: " + target);
187: }
188: }
189: }
190: throw new StepFailedException("No label found with text \""
191: + labelText + "\"", this );
192: }
193:
194: /**
195: * Sets a field according to the step.
196: * It is up to the step's implementation to decide how to set the step.
197: *
198: * @param field The field to set.
199: */
200: protected abstract void setField(HtmlElement field)
201: throws IOException;
202:
203: /**
204: * Finds the relevant form.
205: *
206: * @return The found form.
207: */
208: protected abstract HtmlForm findForm();
209:
210: /**
211: * Finds all possible input fields. This is a generic implementation, sub-classes may want to take advantage
212: * of more specific functions.
213: *
214: * @param form The form to search.
215: * @return A list of candidate fields.
216: */
217: protected List findFields(final HtmlForm form) {
218: return form.getInputsByName(fName);
219: }
220:
221: /**
222: * Apply {@link #keepField(com.gargoylesoftware.htmlunit.html.HtmlElement)} to trim the list of fields found.
223: *
224: * @param fields All fields found.
225: * @return A list of candidate fields.
226: */
227: protected List trimFields(final List fields) {
228: for (final Iterator iter = fields.iterator(); iter.hasNext();) {
229: final HtmlElement elt = (HtmlElement) iter.next();
230: LOG.debug("Considering element " + elt);
231: if (!keepField(elt)) {
232: iter.remove();
233: }
234: }
235: LOG.debug("Found " + fields.size() + " field(s)");
236: return fields;
237: }
238:
239: /**
240: * Called by {@link #findFields(com.gargoylesoftware.htmlunit.html.HtmlForm)} to filter out elements
241: * with the correct name but not matching some other selection criteria.
242: * @param elt One of the elements with the correct name.
243: * @return True if the element is accepted.
244: */
245: protected boolean keepField(HtmlElement elt) {
246: return true;
247: }
248:
249: /**
250: * Finds the desired field by selecting either a specific field designated by
251: * indexStr or the first one if indexStr is left blank
252: *
253: * @param fieldList A list of {@link HtmlElement fields}.
254: * @param indexStr The index of the desired field.
255: * @param step The calling step, for exception.
256: * @return The selected field
257: */
258: public static HtmlElement selectField(final List fieldList,
259: final String indexStr, final Step step) {
260: if (fieldList.isEmpty()) {
261: throw new StepFailedException("No suitable field(s) found",
262: step);
263: }
264:
265: int numFieldsFound = fieldList.size();
266: int index;
267: if (StringUtils.isEmpty(indexStr)) {
268: LOG
269: .info("Found "
270: + numFieldsFound
271: + " suitable fields, considering only the first one");
272: index = 0;
273: } else {
274: index = ConversionUtil.convertToInt(indexStr, 0);
275: if (index < 0 || index >= numFieldsFound) {
276: throw new StepFailedException(
277: "Can't set field with index '" + index
278: + "', valid range is 0.."
279: + (numFieldsFound - 1), step);
280: }
281: }
282: return (HtmlElement) fieldList.get(index);
283: }
284:
285: protected void verifyParameters() {
286: super .verifyParameters();
287:
288: nullResponseCheck();
289:
290: int count = 0;
291: if (fXPath != null) {
292: count++;
293: }
294: if (fName != null) {
295: count++;
296: }
297: if (fHtmlId != null) {
298: count++;
299: }
300: if (getForLabel() != null) {
301: count++;
302: }
303: paramCheck(count == 0, MESSAGE_ARGUMENT_MISSING);
304: paramCheck(count > 1, MESSAGE_ARGUMENT_REDUNDANT);
305: if (fName == null) {
306: paramCheck(fFieldIndex != null,
307: "The attribute 'fieldIndex' is only valid with the attribute 'name'.");
308: } else {
309: optionalIntegerParamCheck(fFieldIndex, "fieldIndex", false);
310: }
311: }
312: }
|