// Copyright � 2002-2007 Canoo Engineering AG, Switzerland.
package com.canoo.webtest.steps.request;
import com.canoo.webtest.engine.StepFailedException;
import com.canoo.webtest.util.HtmlConstants;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.ClickableElement;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.xml.xpath.XPathException;
/**
* Clicks a link determined by its id or its label and/or its href.
*
* @author Mister Unknown
* @author Marc Guillemot
* @author Matt Raible
* @author Paul King
* @author Denis N. Antonioli
* @webtest.step category="Core"
* name="clickLink"
* alias="clicklink"
* 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."
*/
public class ClickLink extends AbstractIdOrLabelTarget {
private static final Logger LOG = Logger.getLogger(ClickLink.class);
private String fHref;
public String getHref() {
return fHref;
}
/**
* @webtest.parameter required="yes/no"
* 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."
*/
public void setHref(final String href) {
fHref = href;
}
protected Page findTarget() throws XPathException, IOException {
final HtmlPage currentResp = getContext().getCurrentHtmlResponse(this);
final HtmlAnchor link = (HtmlAnchor) findClickableElement(currentResp);
if (link == null)
{
final StepFailedException e = new StepFailedException("Link not found in page " + currentResp.getWebResponse().getRequestUrl(), this);
final StringBuffer sb = new StringBuffer();
for (final Iterator iter = currentResp.getAnchors().iterator(); iter.hasNext();) {
final HtmlAnchor webLink = (HtmlAnchor) iter.next();
sb.append("- label \"").append(webLink.asText());
sb.append("\" with url \"").append(webLink.getHrefAttribute());
sb.append("\" and id \"").append(webLink.getIdAttribute());
sb.append("\"\n");
}
e.addDetail("available links", sb.toString());
throw e;
}
LOG.debug("Clicking on link with href: " + link.getHrefAttribute());
return link.click();
}
protected String getLogMessageForTarget() {
return "by clickLink";
}
protected void verifyParameters() {
super.verifyParameters();
nullResponseCheck();
int count = 0;
if (getXpath() != null) {
count++;
}
if (getHtmlId() != null) {
count++;
}
if (getHref() != null || getLabel() != null) {
count++;
}
paramCheck(count == 0, "\"htmlId\" or \"xpath\" or \"label\" or \"href\" must be set!");
paramCheck(count > 1, "\"htmlId\", \"xpath\" and \"label\" or \"href\" can't be combined!");
}
/**
* get the link corresponding to the indications defined in the class
*
* @param page
* @return <code>null</code> if no link found
* @deprecated Use {@link #findClickableElement(com.gargoylesoftware.htmlunit.html.HtmlPage)} .
*/
protected HtmlAnchor locateWebLink(final HtmlPage page) throws XPathException {
return (HtmlAnchor) findClickableElement(page);
}
protected ClickableElement findClickableElementByAttribute(HtmlPage page) {
HtmlAnchor link = locateTextLink(page);
if (link != null) {
return link;
}
return getLinkWithImageText(page, getLabel());
}
/**
* Returns the first link which contains an image with the specified text as its 'alt' attribute.
*
* @param htmlPage
* @param text
* @return <code>null</code> if none found
*/
protected static HtmlAnchor getLinkWithImageText(final HtmlPage htmlPage, final String text) {
final List li = htmlPage.getDocumentElement().getElementsByAttribute(HtmlConstants.IMG, HtmlConstants.ALT, text);
LOG.debug("Found " + li.size() + " images with alt=\"" + text + "\"");
// looking for the first enclosing link
for (final Iterator iter = li.iterator(); iter.hasNext();) {
final HtmlElement elt = (HtmlElement) iter.next();
final HtmlAnchor link = (HtmlAnchor) findParent("a", elt);
if (link != null) {
return link;
}
}
return null;
}
/**
* Finds the first parent element with the given tag name
*
* @param tagName
* @param elt
* @return <code>null</code> if none found
*/
private static HtmlElement findParent(final String tagName, final HtmlElement elt) {
HtmlElement parent = (HtmlElement) elt.getParentNode();
while (parent != null) {
if (tagName.equals(parent.getTagName())) {
return parent;
}
final Object o = parent.getParentNode();
parent = o instanceof HtmlElement ? (HtmlElement) o : null;
}
return null;
}
/**
* @return null if not found
*/
protected HtmlAnchor locateTextLink(final HtmlPage currentResponse) {
for (final Iterator iter = currentResponse.getAnchors().iterator(); iter.hasNext();) {
final HtmlAnchor curLink = (HtmlAnchor) iter.next();
if (isMatching(curLink)) {
return curLink;
}
}
return null;
}
protected boolean isMatching(final HtmlAnchor link) {
boolean bRep = true;
if (getLabel() != null) {
bRep = link.asText().indexOf(getLabel()) >= 0;
}
LOG.debug("labelFound = " + bRep + " in " + link.asText());
if (getHref() == null) {
return bRep;
}
boolean bHrefFound = link.getHrefAttribute().indexOf(getHref()) >= 0;
LOG.debug("hrefFound = " + bHrefFound + " in " + link.getHrefAttribute());
return bRep && bHrefFound;
}
ClickableElement checkFoundElement(HtmlElement elt) throws StepFailedException {
if (elt instanceof HtmlAnchor) {
return (ClickableElement) elt;
}
throw new StepFailedException("Selected element is a " + elt.getTagName() + " tag and not a link", this);
}
/**
* Called by Ant to set the text nested between opening and closing tags.
* @param text the text to set
* @webtest.nested.parameter
* required="no"
* description="Alternative way to set the 'label' attribute."
*/
public void addText(final String text) {
setLabel(getProject().replaceProperties(text));
}
}