001: // Copyright © 2002-2007 Canoo Engineering AG, Switzerland.
002: package com.canoo.webtest.steps.form;
003:
004: import java.util.Iterator;
005: import java.util.List;
006:
007: import org.apache.log4j.Logger;
008:
009: import com.canoo.webtest.engine.IStringVerifier;
010: import com.canoo.webtest.engine.StepExecutionException;
011: import com.canoo.webtest.engine.StepFailedException;
012: import com.canoo.webtest.steps.request.TargetHelper;
013: import com.canoo.webtest.util.ConversionUtil;
014: import com.canoo.webtest.util.FormUtil;
015: import com.canoo.webtest.util.HtmlConstants;
016: import com.gargoylesoftware.htmlunit.ElementNotFoundException;
017: import com.gargoylesoftware.htmlunit.Page;
018: import com.gargoylesoftware.htmlunit.html.HtmlElement;
019: import com.gargoylesoftware.htmlunit.html.HtmlForm;
020: import com.gargoylesoftware.htmlunit.html.HtmlOption;
021: import com.gargoylesoftware.htmlunit.html.HtmlSelect;
022:
023: /**
024: * Selects one or many elements of a select field ( <select name="foo">...
025: * </select>)
026: *
027: * @author Marc Guillemot
028: * @webtest.step category="Core"
029: * name="setSelectField"
030: * alias="new_setselectfield,setselectfield"
031: * description="Provides the ability to update select fields in <key>HTML</key> forms."
032: */
033: public class SetSelectField extends AbstractSetNamedFieldStep {
034: private static final Logger LOG = Logger
035: .getLogger(SetSelectField.class);
036: static final String MESSAGE_MISSING_OPTION_IDENTIFIER = "Either \"value\", \"text\" or \"optionIndex\" is required to identify option in select identified by \"htmlId\" or \"xpath\"!";
037: static final String AT_MOST_ONE_VALUE_TEXT_OPTIONINDEX = "At most one of \"value\", \"text\" or \"optionIndex\" can be set!";
038:
039: private boolean fIsMultiSelect, fIsRegex;
040: private String fMultiSelect;
041: private String fRegex;
042: private String fText;
043: private String fOptionIndex;
044: private String fUserName;
045: private String fPassword;
046: private String fSavePrefix;
047: private String fSaveResponse;
048: private final TargetHelper fTargetHelper = new TargetHelper(this );
049:
050: /**
051: * Gets the value of the regex attribute. The value is only available after the parameter has been validated.
052: * @return <code>true</code> if {@link #getText()} is a regular expression
053: */
054: public boolean isRegex() {
055: return fIsRegex;
056: }
057:
058: /**
059: * Gets the value of the multiSelect attribute. The value is only available after the parameter has been validated.
060: * @return <code>true</code> if multiple selection is allowed
061: */
062: public boolean isMultiSelect() {
063: return fIsMultiSelect;
064: }
065:
066: /**
067: * @webtest.parameter required="false"
068: * default="false"
069: * description="Specifies whether multiple selections are allowed. Unless set to true, every setselect overrides the value of preceding calls."
070: */
071: public void setMultiselect(final String multiSelect) {
072: fMultiSelect = multiSelect;
073: }
074:
075: public String getMultiselect() {
076: return fMultiSelect;
077: }
078:
079: /**
080: * @webtest.parameter required="no"
081: * default="false"
082: * description="Specifies whether the option text represents a regular expression."
083: */
084: public void setRegex(final String regex) {
085: fRegex = regex;
086: }
087:
088: public String getRegex() {
089: return fRegex;
090: }
091:
092: /**
093: * @webtest.parameter required="yes/no"
094: * description="The text of the option to select (i.e. the text nested in the option tag. One of <em>text</em>, <em>value</em> or <em>optionIndex</em> is required."
095: */
096: public void setText(final String text) {
097: fText = text;
098: }
099:
100: public String getText() {
101: return fText;
102: }
103:
104: /**
105: * @param index The new index value
106: * @webtest.parameter required="yes/no"
107: * description="The index of the option to select (i.e. the position of the option in the select starting with 0). One of <em>text</em>, <em>value</em> or <em>optionIndex</em> is required."
108: */
109: public void setOptionIndex(final String index) {
110: fOptionIndex = index; // option index (do we eventually need both of these?)
111: }
112:
113: public String getOptionIndex() {
114: return fOptionIndex;
115: }
116:
117: /**
118: * @param userName
119: * @webtest.parameter required="no"
120: * description="A username that can be provided for pages that require basic authentication. Only needed if setting the select field invokes <key>javascript</key> and causes the page to move to a secure page. Required if <em>password</em> is specified."
121: */
122: public void setUserName(String userName) {
123: fUserName = userName;
124: }
125:
126: public String getUserName() {
127: return fUserName;
128: }
129:
130: /**
131: * @param password
132: * @webtest.parameter required="no"
133: * description="A password that can be provided for pages that require basic authentication. Required if <em>userName</em> is specified."
134: */
135: public void setPassword(String password) {
136: fPassword = password;
137: }
138:
139: public String getPassword() {
140: return fPassword;
141: }
142:
143: /**
144: * @webtest.parameter required="no"
145: * default="the 'savePrefix' parameter as specified in <config>."
146: * description="A name prefix can be specified for making a permanent copy of any received responses. Only needed if setting the select field invokes <key>javascript</key> which causes the browser to move to another page."
147: */
148: public void setSavePrefix(String prefix) {
149: fSavePrefix = prefix;
150: }
151:
152: public String getSavePrefix() {
153: return fSavePrefix;
154: }
155:
156: /**
157: * @webtest.parameter required="no"
158: * description="Whether to make a permanent copy of any received responses. Overrides the default value set in the <config> element. Only needed if setting the select field invokes <key>javascript</key> which causes the browser to move to another page."
159: */
160: public void setSaveResponse(String response) {
161: fSaveResponse = response;
162: }
163:
164: public String getSaveResponse() {
165: return fSaveResponse;
166: }
167:
168: public String getSave() {
169: return null;
170: }
171:
172: /**
173: * @param index
174: * @deprecated use setOptionIndex instead
175: */
176: public void setIndex(final String index) {
177: LOG.warn("setIndex is deprecated - use setOptionIndex instead");
178: setOptionIndex(index);
179: }
180:
181: /**
182: * Set the value
183: *
184: * @param value
185: * @webtest.parameter required="yes/no"
186: * description="The value of the option to select (i.e. the value of the \"value\" attribute of a select element). One of <em>text</em>, <em>value</em> or <em>optionIndex</em> is required."
187: */
188: public void setValue(final String value) {
189: super .setValue(value);
190: }
191:
192: protected HtmlForm findForm() {
193: return FormUtil.findFormForField(getContext(), getFormName(),
194: HtmlConstants.SELECT, null, getName(), this );
195: }
196:
197: /**
198: * Finds all relevant fields with the given name in the form.
199: *
200: * @param form The form to search
201: * @return The list of fields with the given name
202: */
203: protected List findFields(final HtmlForm form) {
204: return form.getSelectsByName(getName());
205: }
206:
207: protected void setField(final HtmlElement elt) {
208: final HtmlSelect select;
209: final HtmlOption option;
210: if (elt instanceof HtmlOption) {
211: option = (HtmlOption) elt;
212: select = (HtmlSelect) option
213: .getEnclosingElement(HtmlConstants.SELECT); // TODO: can be simplified with next htmlunit build
214: } else if (elt instanceof HtmlSelect) {
215: select = (HtmlSelect) elt;
216: // if htmlId or xpath specified, we know now first that text, value or optionIndex is needed
217: if (getText() == null && getOptionIndex() == null
218: && getValue() == null)
219: throw new StepExecutionException(
220: MESSAGE_MISSING_OPTION_IDENTIFIER, this );
221: option = findMatchingOption(select);
222: } else {
223: throw new StepFailedException("Found " + elt.getTagName()
224: + " when looking for select", this );
225: }
226:
227: if (select.isMultipleSelectEnabled() && !fIsMultiSelect) {
228: deselectOtherOptions(select, option);
229: }
230: updateOption(select, option);
231: }
232:
233: void updateOption(final HtmlSelect select, final HtmlOption option) {
234: if (option == null) {
235: throw new StepFailedException(
236: "No option found matching criteria in select "
237: + select);
238: }
239: fTargetHelper.setUsername(getUserName());
240: fTargetHelper.setPassword(getPassword());
241: maybeTarget(option.getPage(), select, option);
242: }
243:
244: protected void maybeTarget(final Page page,
245: final HtmlSelect select, final HtmlOption option) {
246: LOG.debug("Selected option: " + option);
247: select.setSelectedAttribute(option, true);
248: }
249:
250: private static void deselectOtherOptions(final HtmlSelect select,
251: final HtmlOption option) {
252: for (final Iterator iter = select.getOptions().iterator(); iter
253: .hasNext();) {
254: final HtmlOption curOpt = (HtmlOption) iter.next();
255: if (curOpt != option && curOpt.isSelected()) {
256: curOpt.setSelected(false);
257: }
258: }
259: }
260:
261: /**
262: * Selects the option specified by value, text or index and returns it.<p>
263: *
264: * @param select the <select> containing the option
265: * @return the selected option
266: */
267: HtmlOption findMatchingOption(final HtmlSelect select)
268: throws StepExecutionException {
269: LOG.debug("Searching for the right option in " + select);
270: if (getValue() != null) {
271: LOG.debug("Searching option with value: " + getValue());
272: try {
273: return select.getOptionByValue(getValue());
274: } catch (final ElementNotFoundException enfe) {
275: LOG.debug(enfe.getMessage());
276: return null;
277: }
278: } else if (getText() != null) {
279: LOG.debug("Searching option with text: " + getText());
280: return getOptionForText(select, getText());
281: } else {
282: LOG.debug("Searching option with index: "
283: + getOptionIndex());
284: return (HtmlOption) select.getOptions().get(
285: ConversionUtil.convertToInt(getOptionIndex(), 0));
286: }
287: }
288:
289: /**
290: * Search in the select the first option element that has the given
291: * text <p/>
292: *
293: * @param select the select in which to search
294: * @param text The text representing a particular value
295: * @return The option element corresponding to the specified text
296: */
297: private HtmlOption getOptionForText(final HtmlSelect select,
298: final String text) {
299: final IStringVerifier verifier = getVerifier(fIsRegex);
300: for (final Iterator iter = select.getOptions().iterator(); iter
301: .hasNext();) {
302: final HtmlOption option = (HtmlOption) iter.next();
303: LOG.debug("Examining option: " + option);
304: if (verifier.verifyStrings(text, option.asText())) {
305: LOG.debug("Found option by text: " + option);
306: return option;
307: }
308: }
309: throw new StepFailedException(
310: "No option element found with text \"" + text + "\"",
311: this );
312: }
313:
314: protected void verifyParameters() {
315: super .verifyParameters();
316:
317: int iNbNotNull = 0;
318: if (!isValueNull()) {
319: ++iNbNotNull;
320: }
321: if (getText() != null) {
322: ++iNbNotNull;
323: }
324: if (getOptionIndex() != null) {
325: integerParamCheck(getOptionIndex(), "optionIndex", false);
326: ++iNbNotNull;
327: }
328: final boolean bXPathOrId = getXpath() != null
329: || getHtmlId() != null;
330:
331: paramCheck(iNbNotNull > 1, AT_MOST_ONE_VALUE_TEXT_OPTIONINDEX);
332: paramCheck(
333: !bXPathOrId && iNbNotNull == 0,
334: "One of \"xpath\", \"htmlId\", \"value\", \"text\" or \"optionIndex\" is required!");
335: fIsMultiSelect = ConversionUtil.convertToBoolean(
336: getMultiselect(), false);
337: fIsRegex = ConversionUtil.convertToBoolean(getRegex(), false);
338: }
339: }
|