com.technophobia.webdriver.substeps.impl.FinderWebDriverSubStepImplementations.java Source code

Java tutorial

Introduction

Here is the source code for com.technophobia.webdriver.substeps.impl.FinderWebDriverSubStepImplementations.java

Source

/*
 *   Copyright Technophobia Ltd 2012
 *
 *   This file is part of Substeps.
 *
 *    Substeps is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Substeps is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with Substeps.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.technophobia.webdriver.substeps.impl;

import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ByChained;
import org.openqa.selenium.StaleElementReferenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Supplier;
import com.technophobia.substeps.model.SubSteps.Step;
import com.technophobia.substeps.model.SubSteps.StepImplementations;
import com.technophobia.substeps.model.SubSteps.StepParameter;
import com.technophobia.substeps.model.parameter.IntegerConverter;
import com.technophobia.webdriver.substeps.runner.DefaultExecutionSetupTearDown;
import com.technophobia.webdriver.util.WebDriverContext;
import com.technophobia.webdriver.util.WebDriverSubstepsBy;

@StepImplementations(requiredInitialisationClasses = DefaultExecutionSetupTearDown.class)
public class FinderWebDriverSubStepImplementations extends AbstractWebDriverSubStepImplementations {

    private static final Logger logger = LoggerFactory.getLogger(FinderWebDriverSubStepImplementations.class);

    /**
     * Find an element by it's ID
     * 
     * @example FindById username
     * @section Location
     * 
     * @param id
     *            the id
     * @return the web element
     */
    @Step("FindById ([^\"]*)")
    public WebElement findById(final String id) {

        logger.debug("Looking for item with id " + id);
        webDriverContext().setCurrentElement(null);

        final WebElement elem = webDriverContext().waitForElement(By.id(id));
        Assert.assertNotNull("expecting an element with id " + id, elem);

        webDriverContext().setCurrentElement(elem);
        return elem;
    }

    /**
     * Find an element by it's ID with the specified timeout
     * 
     * @example FindByIdTimeout username timeout = 15 secs
     * @section Location
     * 
     * @param id
     *            the id
     * @param timeout
     *            the timeout
     * @return the web element
     */
    @Step("FindByIdTimeout ([^\"]*) timeout = ([^\"]*) secs")
    public WebElement findById(final String id, final String timeout) {
        logger.debug("Looking for item with id " + id + "within " + timeout + " seconds");
        final long t = Long.parseLong(timeout);

        webDriverContext().setCurrentElement(null);
        final WebElement elem = webDriverContext().waitForElement(By.id(id), t);
        Assert.assertNotNull("expecting an element with id " + id, elem);
        webDriverContext().setCurrentElement(elem);
        return elem;
    }

    // xpath query //*[contains(@id, 'f41_txt')]
    // $x("//*[@id='sogei_mapping_left_container']")

    // $x("//*[@id='sogei_mapping_left_container']//div[contains(text(),'BANDY')]")  selects the item

    // Handy little ! xpath to get to the chevron to do the whatsit
    // $x("//*[@id='sogei_mapping_left_container']//div[contains(text(),'BASKET')]/ancestor::div[contains(@class, 'mapperItem draggable')]//div[contains(@class, 'chevron')]")

    /**
     * Finds an element using an xpath expression, the expression may contain quotes, unlike an earlier implementation.
     * Xpath expressions should search rather than full paths to elements
     *
     *  @example FindByXpath //li[a/i[contains(@class, "NOT_RUN")]]
     *  @section Finders
     *
     * @param xpath the xpath expression
     * @return the web element found
     */

    @Step("FindByXpath (.*)$")
    public WebElement findByXpath(final String xpath) {
        logger.debug("Looking for item with xpath " + xpath);
        webDriverContext().setCurrentElement(null);
        final WebElement elem = webDriverContext().waitForElement(By.xpath(xpath));
        Assert.assertNotNull("expecting an element with xpath " + xpath, elem);
        webDriverContext().setCurrentElement(elem);
        return elem;
    }

    /**
     * Find an element using the name attribute of the element
     * 
     * @example FindByName "named field"
     * @section Location
     * 
     * @param name
     *            the name
     * @return the web element
     */
    @Step("FindByName \"?([^\"]*)\"?")
    public WebElement findByName(final String name) {
        logger.debug("Looking for item with name " + name);
        webDriverContext().setCurrentElement(null);
        final WebElement elem = webDriverContext().waitForElement(By.name(name));
        Assert.assertNotNull("expecting an element with name " + name, elem);
        webDriverContext().setCurrentElement(elem);
        return elem;
    }

    /**
     * Finds an element on the page with the specified tag and containing the specified text
     * 
     * @example FindFirstTagElementContainingText tag="ul"
     *          text="list item itext"
     * @section Location
     * @param tag
     *            the tag
     * @param text
     *            the text
     */
    @Step("FindFirstTagElementContainingText tag=\"([^\"]*)\" text=\"([^\"]*)\"")
    public void findFirstTagElementContainingText(final String tag, final String text) {
        logger.debug("Finding tag element " + tag + "and asserting has text " + text);

        webDriverContext().setCurrentElement(null);

        final By by = WebDriverSubstepsBy.ByTagContainingText(tag, text);

        final WebElement elem = MatchingElementResultHandler.AtLeastOneElement.processResults(webDriverContext(),
                by, "expecting at least one child element to contain text: " + text);

        webDriverContext().setCurrentElement(elem);
    }

    //    /**
    //     * Finds an element on the page with the specified tag and text
    //     *
    //     * @example FindTagElementContainingText tag="ul" text="list item itext"
    //     * @section Location
    //     * @param tag
    //     *            the tag
    //     * @param text
    //     *            the text
    //     */
    //    @Step("FindTagElementWithExactText tag=\"([^\"]*)\" text=\"([^\"]*)\"")
    //    public void findTagElementWithExactText(final String tag, final String text) {
    //        logger.debug("Finding tag element " + tag + "and asserting has exact text " + text);
    //
    //        webDriverContext().setCurrentElement(null);
    //        final List<WebElement> elementsWithTagName = webDriver().findElements(By.tagName(tag));
    //
    //        WebElement matchingElement = null;
    //        for (final WebElement element : elementsWithTagName) {
    //
    //            if (element.getText().equals(text)) {
    //
    //                if (matchingElement == null) {
    //                    matchingElement = element;
    //                } else {
    //                    Assert.fail("expected one element with tag " + tag + " and text " + text + " but found multiple");
    //                }
    //            }
    //        }
    //
    //        Assert.assertNotNull("expecting element with tag " + tag + " and text " + text, matchingElement);
    //        webDriverContext().setCurrentElement(matchingElement);
    //    }

    /**
     * Finds an element that is a child of the current element using the name
     * attribute, another Find method should be used first
     * 
     * @example FindChild ByName name="child name"
     * @section Location
     * 
     * @param name
     *            the name
     * @return the web element
     */
    @Step("FindChild ByName name=\"?([^\"]*)\"?")
    public WebElement findChildByName(final String name) {
        logger.debug("Looking for child with name " + name);
        Assert.assertNotNull("expecting a current element", webDriverContext().getCurrentElement());
        final WebElement elem = webDriverContext().getCurrentElement().findElement(By.name(name));

        Assert.assertNotNull("expecting an element with name " + name, elem);
        webDriverContext().setCurrentElement(elem);
        return elem;
    }

    /**
     * Finds an element that is a child of the current element using the tag
     * name and specified attributes, another Find method should be used first
     * 
     * @example FindChild ByTagAndAttributes tag="input"
     *          attributes=[type="submit",value="Search"]
     * @section Location
     * 
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @return the web element
     */
    @Step("FindChild ByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\]")
    public WebElement findChildByTagAndAttributes(final String tag, final String attributeString) {
        logger.debug("Looking for child with tag " + tag + " and attributes " + attributeString);

        return findChildByTagAndAttributes(tag, attributeString, MatchingElementResultHandler.ExactlyOneElement);
    }

    private WebElement findChildByTagAndAttributes(final String tag, final String attributeString,
            final MatchingElementResultHandler resultHandler) {

        final WebElement currentElement = webDriverContext().getCurrentElement();

        final By byTagAndAttributes = WebDriverSubstepsBy.ByTagAndAttributes(tag, attributeString);

        final By byCurrentElement = WebDriverSubstepsBy.ByCurrentWebElement(currentElement);

        final By chained = new ByChained(byCurrentElement, byTagAndAttributes);

        final String msg = "failed to locate a child element with tag: " + tag + " and attributes: "
                + attributeString;

        final WebElement elem = resultHandler.processResults(webDriverContext(), chained, msg);

        webDriverContext().setCurrentElement(elem);

        return elem;
    }

    /**
     * Finds an element that is a child of the current element using the tag
     * name, specified attributes and text, another Find method should be used first
     * 
     * @example FindChild ByTagAndAttributes tag="input"
     *          attributes=[type="submit",value="Search"] with text="bob"
     * @section Location
     * 
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @param text
     *            the text to look for
     * @return the web element
     */
    @Step("FindChild ByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\] with text=\"([^\"]*)\"")
    public WebElement findChildByTagAndAttributesWithText(final String tag, final String attributeString,
            final String text) {
        logger.debug("Looking for child with tag " + tag + " and attributes " + attributeString);

        return findChildByTagAndAttributes(tag, attributeString, text,
                MatchingElementResultHandler.ExactlyOneElement);
    }

    private WebElement findChildByTagAndAttributes(final String tag, final String attributeString,
            final String text, final MatchingElementResultHandler resultHandler) {

        Assert.assertNotNull("expecting a current element", webDriverContext().getCurrentElement());

        final WebElement currentElement = webDriverContext().getCurrentElement();

        final By byTagAndAttributesWithText = WebDriverSubstepsBy.ByTagAndAttributesWithText(tag, attributeString,
                text);

        final By byCurrentElement = WebDriverSubstepsBy.ByCurrentWebElement(currentElement);

        final By chained = new ByChained(byCurrentElement, byTagAndAttributesWithText);

        final String msg = "failed to locate a child element with tag: " + tag + ", attributes: " + attributeString
                + " and text: " + text;

        final WebElement elem = resultHandler.processResults(webDriverContext(), chained, msg);

        webDriverContext().setCurrentElement(elem);

        return elem;
    }

    /**
     * Finds the first child element of the 'current' element using the tag name
     * and specified attributes, another Find method should be used first
     * 
     * @example FindFirstChild ByTagAndAttributes tag="input"
     *          attributes=[type="submit",value="Search"]
     * @section Location
     * 
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @return the web element
     */
    @Step("FindFirstChild ByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\]")
    public WebElement findFirstChildByTagAndAttributes(final String tag, final String attributeString) {

        logger.debug("Looking for first child with tag " + tag + " and attributes " + attributeString);

        return findChildByTagAndAttributes(tag, attributeString, MatchingElementResultHandler.AtLeastOneElement);

    }

    /**
     * Finds a checkbox that is a child of the specified tag, that contains the
     * specified text; eg.
     *
     &lt;pre&gt;
     &lt;label&gt;
     &lt;input type=&quot;checkbox&quot; name=&quot;checkbox_name&quot; value=&quot;yeah&quot;/&gt;a checkbox &lt;span&gt;label&lt;/span&gt;
     &lt;/label&gt;
     &lt;/pre&gt;
     * 
     * @example FindCheckbox inside tag="label" with label="a checkbox label"
     * 
     * @section Location
     * 
     * @param tag
     *            the tag
     * @param label
     *            the checkbox label
     * @return the web element
     */
    @Step("FindCheckbox inside tag=\"?([^\"]*)\"? with label=\"([^\"]*)\"")
    public WebElement findCheckBox(final String tag, final String label) {

        return findInputInsideTag(label, tag, "checkbox");

    }

    // todo variant that also has attributes for the tag

    /**
     * Finds a radiobutton that is a child of the specified tag, that contains
     * the specified text; eg.
     *
     &lt;pre&gt;
     &lt;label&gt;
     &lt;input type=&quot;radio&quot; name=&quot;radio_name&quot; value=&quot;yeah&quot;/&gt;a radio &lt;span&gt;label&lt;/span&gt;
     &lt;/label&gt;
     &lt;/pre&gt;
     *
     * @example FindRadioButton inside tag="label" with label="a radio label"
     * 
     * @section Location
     * 
     * @param tag
     *            the tag
     * @param label
     *            the radio button label
     * @return the web element
     */
    @Step("FindRadioButton inside tag=\"?([^\"]*)\"? with label=\"([^\"]*)\"")
    public WebElement findRadioButton(final String tag, final String label) {

        return findInputInsideTag(label, tag, "radio");
    }

    /**
     * @param label the label of the input
     * @param tag the tag
     * @param inputType the input type
     * @return the web element
     */
    public WebElement findInputInsideTag(final String label, final String tag, final String inputType) {
        WebElement elem = null;
        webDriverContext().setCurrentElement(null);

        // TODO - turn this into a function

        // look for elems with the right tag
        final List<WebElement> tagElems = webDriver().findElements(By.tagName(tag));

        checkElements("expecting some elements of tag: " + tag, tagElems);

        List<WebElement> matchingElems = null;

        for (final WebElement tagElem : tagElems) {

            // does this tag contain the right text ?
            if (label.compareTo(tagElem.getText()) == 0) {
                // yes
                // is there a radio button inside ?

                final List<WebElement> inputElements = tagElem.findElements(By.tagName("input"));

                if (inputElements != null && !inputElements.isEmpty()) {
                    // are they radio buttons ?

                    for (final WebElement inputElement : inputElements) {
                        final String type = inputElement.getAttribute("type");

                        if (type != null && type.compareToIgnoreCase(inputType) == 0) {
                            // bingo
                            if (matchingElems == null) {
                                matchingElems = new ArrayList<WebElement>();
                            }
                            matchingElems.add(inputElement);
                        }
                    }
                }
            }
        }

        elem = MatchingElementResultHandler.checkForOneMatchingElement(
                "expecting an input of type " + inputType + " inside tag [" + tag + "] with label [" + label + "]",
                matchingElems);

        webDriverContext().setCurrentElement(elem);
        return elem;
    }

    /**
     * @param msg the assertion message
     * @param tagElems the element list to be checked for empty or null
     */
    public void checkElements(final String msg, final List<WebElement> tagElems) {
        Assert.assertNotNull(msg, tagElems);
        Assert.assertTrue(msg, !tagElems.isEmpty());
    }

    /**
     * Find an element by tag name and a set of attributes and corresponding
     * values
     * 
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @return the web element
     * @example FindByTagAndAttributes tag="input"
     *          attributes=[type="submit",value="Search"]
     * @section Location
     */
    @Step("FindByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\]")
    public WebElement findByTagAndAttributes(final String tag, final String attributeString) {
        logger.debug("Looking for item with tag " + tag + " and attributes " + attributeString);

        return findByTagAndAttributes(tag, attributeString, MatchingElementResultHandler.ExactlyOneElement);
    }

    private WebElement findByTagAndAttributes(final String tag, final String attributeString,
            final MatchingElementResultHandler handler) {

        webDriverContext().setCurrentElement(null);

        WebElement rtn = null;

        final By by = WebDriverSubstepsBy.ByTagAndAttributes(tag, attributeString);

        final String msg = "failed to locate an element with tag: " + tag + " and attributes: " + attributeString;

        rtn = handler.processResults(webDriverContext(), by, msg);

        webDriverContext().setCurrentElement(rtn);

        return rtn;
    }

    /**
     * Finds the first element by tag name and a set of attributes and
     * corresponding values
     * 
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @return the web element
     * @example FindFirstByTagAndAttributes tag="input"
     *          attributes=[type="submit",value="Search"]
     * @section Location
     */
    @Step("FindFirstByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\]")
    public WebElement findFirstByTagAndAttributes(final String tag, final String attributeString) {
        logger.debug("Looking for first item with tag " + tag + " and attributes " + attributeString);

        return findByTagAndAttributes(tag, attributeString, MatchingElementResultHandler.AtLeastOneElement);
    }

    /**
     * Finds the n th element by tag name and a set of attributes and
     * corresponding values
     * 
     * @param nth
     *            the nth matching element we wish to find
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @return the web element
     * @example FindNthByTagAndAttributes n=2 tag="input"
     *          attributes=[type="submit",value="Search"]
     * @section Location
     */
    @Step("FindNthByTagAndAttributes n=\"?([^\"]*)\"? tag=\"?([^\"]*)\"? attributes=\\[(.*)\\]")
    public WebElement findNthByTagAndAttributes(
            @StepParameter(converter = IntegerConverter.class) final Integer nth, final String tag,
            final String attributeString) {
        logger.debug("Looking for nth item with tag " + tag + " and attributes " + attributeString);

        webDriverContext().setCurrentElement(null);

        WebElement rtn = null;

        final By by = WebDriverSubstepsBy.NthByTagAndAttributes(tag, attributeString, nth);

        final String msg = "failed to locate the " + nth.intValue() + "th element with tag: " + tag
                + " and attributes: " + attributeString;

        final MatchingElementResultHandler.NthElement handler = new MatchingElementResultHandler.NthElement(nth);

        rtn = handler.processResults(webDriverContext(), by, msg);

        webDriverContext().setCurrentElement(rtn);

        return rtn;
    }

    /**
     * Finds an element by tag name and a set of attributes and corresponding
     * values, that has a child tag element of the specified type and having the
     * specified text
     * 
     * @param tag
     *            the parent tag
     * @param attributeString
     *            the parent attribute string
     * @param childTag
     *            the child tag
     * @param childText
     *            the child's text
     * 
     * @return the web element
     * @example FindParentByTagAndAttributes tag="table"
     *          attributes=[class="mytable"] ThatHasChild tag="caption"
     *          text="wahoo"
     * @section Location
     */
    @Step("FindParentByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\] ThatHasChild tag=\"?([^\"]*)\"? text=\"([^\"]*)\"")
    public WebElement findParentByTagAndAttributesThatHasChildWithTagAndText(final String tag,
            final String attributeString, final String childTag, final String childText) {

        webDriverContext().setCurrentElement(null);

        WebElement rtn = null;

        final By by = WebDriverSubstepsBy.ByTagAndAttributes(tag, attributeString);

        final By childBy = WebDriverSubstepsBy.ByTagAndWithText(childTag, childText);

        final String assertionMessage = "Failed to locate a parent element with tag: " + tag + " and attributes: "
                + attributeString + " with a child element of tag: " + childTag + " with text: " + childText;

        final String findParentAssertionMessage = "failed to locate an element with tag: " + tag
                + " and attributes: " + attributeString;
        final String multipleChildrenMessage = "More than one child element found for parent with tag: " + childTag
                + " and text: " + childText;

        rtn = findParentByWithChildBy(by, childBy, assertionMessage, findParentAssertionMessage,
                multipleChildrenMessage);
        return rtn;
    }

    /**
     * @param by
     * @param childBy
     * @param assertionMessage
     * @param findParentAssertionMessage
     * @param multipleChildrenMessage
     * @return
     */
    private WebElement findParentByWithChildBy(final By by, final By childBy, final String assertionMessage,
            final String findParentAssertionMessage, final String multipleChildrenMessage) {

        boolean repeat = true;
        WebElement rtn = null;
        while (repeat) {

            repeat = false;
            try {
                rtn = findParentWithChildByInternal(by, childBy, assertionMessage, findParentAssertionMessage,
                        multipleChildrenMessage);
            } catch (StaleElementReferenceException e) {

                logger.debug("stale element exception caught, retrying..");
                repeat = true;
            }
        }
        Assert.assertNotNull(assertionMessage, rtn);
        webDriverContext().setCurrentElement(rtn);
        return rtn;
    }

    private WebElement findParentWithChildByInternal(final By by, final By childBy, final String assertionMessage,
            final String findParentAssertionMessage, final String multipleChildrenMessage) {
        WebElement rtn;
        List<WebElement> matchingElements = null;

        final List<WebElement> candidateParentElements = webDriver().findElements(by);

        Assert.assertNotNull(findParentAssertionMessage, candidateParentElements);
        Assert.assertFalse(findParentAssertionMessage, candidateParentElements.isEmpty());

        for (final WebElement parent : candidateParentElements) {

            final List<WebElement> children = parent.findElements(childBy);

            // do we care if there are more than one matching child ? lets go
            // with no..
            if (children != null && !children.isEmpty()) {

                if (matchingElements == null) {
                    matchingElements = new ArrayList<WebElement>();
                }
                matchingElements.add(parent);
                if (children.size() > 1) {
                    logger.info(multipleChildrenMessage);
                }
            }
        }
        rtn = MatchingElementResultHandler.checkForOneMatchingElement(assertionMessage, matchingElements);
        return rtn;
    }

    /**
     * Finds an element by tag name and a set of attributes and corresponding
     * values, that has a child tag element of the specified type that has the
     * specified attributes..
     * 
     * @param tag
     *            the parent tag
     * @param attributeString
     *            the parent attribute string
     * @param childTag
     *            the child tag
     * @param childAttributeString
     *            the child's attribute string
     * 
     * @return the web element
     * @example FindParentByTagAndAttributes tag="table"
     *          attributes=[class="mytable"] ThatHasChild tag="caption"
     *          attributes=[class="childClass"]
     * @section Location
     */
    @Step("FindParentByTagAndAttributes tag=\"?([^\"]*)\"? attributes=\\[(.*)\\] ThatHasChild tag=\"?([^\"]*)\"? attributes=\\[(.*)\\]")
    public WebElement findParentByTagAndAttributesThatHasChildWithTagAndAttributes(final String tag,
            final String attributeString, final String childTag, final String childAttributeString) {

        webDriverContext().setCurrentElement(null);

        WebElement rtn = null;

        final By by = WebDriverSubstepsBy.ByTagAndAttributes(tag, attributeString);

        final By childBy = WebDriverSubstepsBy.ByTagAndAttributes(childTag, childAttributeString);

        final String assertionMessage = "Failed to locate a parent element with tag: " + tag + " and attributes: "
                + attributeString + " with a child element of tag: " + childTag + " and attributes: "
                + childAttributeString;

        final String findParentAssertionMessage = "failed to locate an element with tag: " + tag
                + " and attributes: " + attributeString;
        final String multipleChildrenMessage = "More than one child element found for parent with tag: " + childTag
                + " and and attributes: " + childAttributeString;

        rtn = findParentByWithChildBy(by, childBy, assertionMessage, findParentAssertionMessage,
                multipleChildrenMessage);
        return rtn;
    }

    /**
     * Gets the element with text.
     * 
     * @param type
     *            the type
     * @param text
     *            the text
     * @return the element with text
     */
    public WebElement findElementWithText(final String type, final String text) {
        WebElement elem = null;
        final List<WebElement> elems = webDriver().findElements(By.tagName(type));
        if (elems != null) {
            for (final WebElement e : elems) {

                if (text.equalsIgnoreCase(e.getText())) {
                    elem = e;
                    break;
                }
            }
        }
        return elem;
    }

    /**
     * Find the element with id that has the text ....
     * 
     * @example FindById msg_id and text = "Hello World"
     * @section Location
     * @param id
     *            the id
     * @param expected
     *            the expected
     */
    @Step("FindById ([^\"]*) and text = \"([^\"]*)\"")
    public void findByIdAndText(final String id, final String expected) {
        logger.debug("Finding element with id " + id + " and text " + expected);

        try {
            webDriverContext().setCurrentElement(null);

            final By byIdAndText = WebDriverSubstepsBy.ByIdAndText(id, expected);

            final WebElement elem = webDriverContext().waitForElement(byIdAndText);

            Assert.assertNotNull("expecting to find an element with id: " + id, elem);
            webDriverContext().setCurrentElement(elem);
        } catch (final TimeoutException e) {
            logger.debug("timed out waiting for id: " + id + " with text: " + expected + " page src:\n"
                    + webDriver().getPageSource());
            throw e;
        }
    }

    /**
     * From the current element, apply the xpath selecting the first child that
     * has the text ...
     * 
     * @example FindFirstChildElementContainingText xpath="li//a" text =
     *          "Log Out"
     * @section Location
     * @param xpath
     *            the xpath expression, NB this is quoted and can't contain double quotes
     * @param text
     *            the text
     */
    @Step("FindFirstChildElementContainingText xpath=\"([^\"]*)\" text=\"([^\"]*)\"")
    public void findChildElementContainingText(final String xpath, final String text) {
        logger.debug("Find child element with xpath " + xpath + " has the text " + text);

        final By byCurrentElement = WebDriverSubstepsBy.ByCurrentWebElement(webDriverContext().getCurrentElement());

        final By chained = new ByChained(byCurrentElement, WebDriverSubstepsBy.ByXpathContainingText(xpath, text));

        webDriverContext().setCurrentElement(null);

        final WebElement elem = MatchingElementResultHandler.AtLeastOneElement.processResults(webDriverContext(),
                chained, "expecting at least one child element to contain text: " + text);

        webDriverContext().setCurrentElement(elem);

    }

    /**
     * Finds the first html tag that starts with the specified text
     * 
     * @example FindTagElementStartingWithText tag="ul" text="list item itext"
     * @section Location
     * @param tag
     *            the tag
     * @param text
     *            the text
     */
    @Step("FindFirstTagElementStartingWithText tag=\"([^\"]*)\" text=\"([^\"]*)\"")
    public void findFirstTagElementStartingWithText(final String tag, final String text) {

        logger.debug("findTagElementStartsWithText tag: " + tag + " has text " + text);

        webDriverContext().setCurrentElement(null);

        final By by = WebDriverSubstepsBy.ByTagStartingWithText(tag, text);

        final WebElement elem = MatchingElementResultHandler.AtLeastOneElement.processResults(webDriverContext(),
                by, "expecting at least one child element to contain text: " + text);

        webDriverContext().setCurrentElement(elem);

    }

    /**
     * Find an element by tag name and a set of attributes and expected text
     * 
     * @param tag
     *            the tag
     * @param attributeString
     *            the attribute string
     * @param text the expected text
     * @return the web element
     * @example FindByTagAndAttributesWithText tag="input" attributes=[type="submit",value="Search"] with text="abc"
     * @section Location
     */
    @Step("FindByTagAndAttributesWithText tag=\"?([^\"]*)\"? attributes=\\[(.*)\\] with text=\"([^\"]*)\"")
    public WebElement findByTagAndAttributesWithText(final String tag, final String attributeString,
            final String text) {
        return findByTagAndAttributesWithText(tag, attributeString, text,
                MatchingElementResultHandler.ExactlyOneElement);
    }

    private WebElement findByTagAndAttributesWithText(final String tag, final String attributeString,
            final String text, final MatchingElementResultHandler handler) {

        webDriverContext().setCurrentElement(null);

        WebElement rtn = null;

        final By by = WebDriverSubstepsBy.ByTagAndAttributesWithText(tag, attributeString, text);

        final String msg = "failed to locate an element with tag: " + tag + ", attributes: " + attributeString
                + ", and text: " + text;

        rtn = handler.processResults(webDriverContext(), by, msg);

        webDriverContext().setCurrentElement(rtn);

        return rtn;
    }

    /**
     * Finds and waits for if necessary, an element by Id containing the specified text
     *
     * @example FindById id containing text="abc"
     * @section Finders
     * @param elementId
     *            HTML ID of element
     * @param text
     *            the expected text
     */
    @Step("FindById ([^\"]*) containing text=\"([^\"]*)\"")
    public void assertEventuallyContains(final String elementId, final String text) {

        waitFor(WebDriverSubstepsBy.ByIdContainingText(elementId, text), String.format(
                "Expected to find a element with 'id=%s' containing text '%s' but didn't.", elementId, text));
    }

}