001: // Copyright © 2002-2007 Canoo Engineering AG, Switzerland.
002: package com.canoo.webtest.steps.request;
003:
004: import com.canoo.webtest.engine.StepFailedException;
005: import com.canoo.webtest.util.HtmlConstants;
006: import com.gargoylesoftware.htmlunit.Page;
007: import com.gargoylesoftware.htmlunit.html.ClickableElement;
008: import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
009: import com.gargoylesoftware.htmlunit.html.HtmlElement;
010: import com.gargoylesoftware.htmlunit.html.HtmlPage;
011: import org.apache.log4j.Logger;
012: import org.jaxen.JaxenException;
013:
014: import java.io.IOException;
015: import java.util.Iterator;
016: import java.util.List;
017:
018: /**
019: * Clicks a link determined by its id or its label and/or its href.
020: *
021: * @author Mister Unknown
022: * @author Marc Guillemot
023: * @author Matt Raible
024: * @author Paul King
025: * @author Denis N. Antonioli
026: * @webtest.step category="Core"
027: * name="clickLink"
028: * alias="clicklink"
029: * description="This step tries to locate a link (like <a href='thisIsAnotherPage.html'>Follow me</a>) and to click on it. The page where this click leads to will be the current one for further actions."
030: */
031: public class ClickLink extends AbstractIdOrLabelTarget {
032: private static final Logger LOG = Logger.getLogger(ClickLink.class);
033: private String fHref;
034:
035: public String getHref() {
036: return fHref;
037: }
038:
039: /**
040: * @webtest.parameter required="yes/no"
041: * description="A substring of the <key>HTML</key> anchor tag's HREF attribute for the link of interest. Useful if (part of the <em>href</em>) uniquely identifies a link (for example, a unique parameter/value pair in the <em>href</em>). Also, useful for distinguisking between links with the same labels (in which case, use <em>label</em> and <em>href</em> combined). Either <em>label</em>, <em>href</em> or <em>htmlId</em> is required. Href has the lowest precedence."
042: */
043: public void setHref(final String href) {
044: fHref = href;
045: }
046:
047: protected Page findTarget() throws JaxenException, IOException {
048: final HtmlPage currentResp = getContext()
049: .getCurrentHtmlResponse(this );
050: final HtmlAnchor link = (HtmlAnchor) findClickableElement(currentResp);
051:
052: if (link == null) {
053: final StepFailedException e = new StepFailedException(
054: "Link not found in page "
055: + currentResp.getWebResponse().getUrl(),
056: this );
057:
058: final StringBuffer sb = new StringBuffer();
059: for (final Iterator iter = currentResp.getAnchors()
060: .iterator(); iter.hasNext();) {
061: final HtmlAnchor webLink = (HtmlAnchor) iter.next();
062:
063: sb.append("- label \"").append(webLink.asText());
064: sb.append("\" with url \"").append(
065: webLink.getHrefAttribute());
066: sb.append("\" and id \"").append(
067: webLink.getIdAttribute());
068: sb.append("\"\n");
069: }
070: e.addDetail("available links", sb.toString());
071: throw e;
072: }
073: LOG.debug("Clicking on link with href: "
074: + link.getHrefAttribute());
075:
076: return link.click();
077: }
078:
079: protected String getLogMessageForTarget() {
080: return "by clickLink";
081: }
082:
083: protected void verifyParameters() {
084: super .verifyParameters();
085: nullResponseCheck();
086:
087: int count = 0;
088: if (getXpath() != null) {
089: count++;
090: }
091: if (getHtmlId() != null) {
092: count++;
093: }
094: if (getHref() != null || getLabel() != null) {
095: count++;
096: }
097: paramCheck(count == 0,
098: "\"htmlId\" or \"xpath\" or \"label\" or \"href\" must be set!");
099: paramCheck(count > 1,
100: "\"htmlId\", \"xpath\" and \"label\" or \"href\" can't be combined!");
101: }
102:
103: /**
104: * get the link corresponding to the indications defined in the class
105: *
106: * @param page
107: * @return <code>null</code> if no link found
108: * @deprecated Use {@link #findClickableElement(com.gargoylesoftware.htmlunit.html.HtmlPage)} .
109: */
110: protected HtmlAnchor locateWebLink(final HtmlPage page) {
111: return (HtmlAnchor) findClickableElement(page);
112: }
113:
114: protected ClickableElement findClickableElementByAttribute(
115: HtmlPage page) {
116: HtmlAnchor link = locateTextLink(page);
117: if (link != null) {
118: return link;
119: }
120: return getLinkWithImageText(page, getLabel());
121: }
122:
123: /**
124: * Returns the first link which contains an image with the specified text as its 'alt' attribute.
125: *
126: * @param htmlPage
127: * @param text
128: * @return <code>null</code> if none found
129: */
130: protected static HtmlAnchor getLinkWithImageText(
131: final HtmlPage htmlPage, final String text) {
132: final List li = htmlPage.getDocumentHtmlElement()
133: .getHtmlElementsByAttribute(HtmlConstants.IMG,
134: HtmlConstants.ALT, text);
135:
136: LOG.debug("Found " + li.size() + " images with alt=\"" + text
137: + "\"");
138:
139: // looking for the first enclosing link
140: for (final Iterator iter = li.iterator(); iter.hasNext();) {
141: final HtmlElement elt = (HtmlElement) iter.next();
142: final HtmlAnchor link = (HtmlAnchor) findParent("a", elt);
143: if (link != null) {
144: return link;
145: }
146: }
147: return null;
148: }
149:
150: /**
151: * Finds the first parent element with the given tag name
152: *
153: * @param tagName
154: * @param elt
155: * @return <code>null</code> if none found
156: */
157: private static HtmlElement findParent(final String tagName,
158: final HtmlElement elt) {
159: HtmlElement parent = (HtmlElement) elt.getParentDomNode();
160:
161: while (parent != null) {
162: if (tagName.equals(parent.getTagName())) {
163: return parent;
164: }
165: final Object o = parent.getParentDomNode();
166: parent = o instanceof HtmlElement ? (HtmlElement) o : null;
167: }
168: return null;
169: }
170:
171: /**
172: * @return null if not found
173: */
174: protected HtmlAnchor locateTextLink(final HtmlPage currentResponse) {
175: for (final Iterator iter = currentResponse.getAnchors()
176: .iterator(); iter.hasNext();) {
177: final HtmlAnchor curLink = (HtmlAnchor) iter.next();
178:
179: if (isMatching(curLink)) {
180: return curLink;
181: }
182: }
183: return null;
184: }
185:
186: protected boolean isMatching(final HtmlAnchor link) {
187: boolean bRep = true;
188:
189: if (getLabel() != null) {
190: bRep = link.asText().indexOf(getLabel()) >= 0;
191: }
192:
193: LOG.debug("labelFound = " + bRep + " in " + link.asText());
194: if (getHref() == null) {
195: return bRep;
196: }
197:
198: boolean bHrefFound = link.getHrefAttribute().indexOf(getHref()) >= 0;
199: LOG.debug("hrefFound = " + bHrefFound + " in "
200: + link.getHrefAttribute());
201: return bRep && bHrefFound;
202: }
203:
204: ClickableElement checkFoundElement(HtmlElement elt)
205: throws StepFailedException {
206: if (elt instanceof HtmlAnchor) {
207: return (ClickableElement) elt;
208: }
209: throw new StepFailedException("Selected element is a "
210: + elt.getTagName() + " tag and not a link", this );
211: }
212:
213: /**
214: * Called by Ant to set the text nested between opening and closing tags.
215: * @param text the text to set
216: * @webtest.nested.parameter
217: * required="no"
218: * description="Alternative way to set the 'label' attribute."
219: */
220: public void addText(final String text) {
221: setLabel(getProject().replaceProperties(text));
222: }
223: }
|