com.mgmtp.jfunk.web.step.JFunkWebElement.java Source code

Java tutorial

Introduction

Here is the source code for com.mgmtp.jfunk.web.step.JFunkWebElement.java

Source

/*
 * Copyright (c) 2015 mgm technology partners GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mgmtp.jfunk.web.step;

import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.mgmtp.jfunk.common.JFunkConstants;
import com.mgmtp.jfunk.common.util.DataUtils;
import com.mgmtp.jfunk.common.util.ElementTrafo;
import com.mgmtp.jfunk.core.exception.StepException;
import com.mgmtp.jfunk.core.exception.ValidationException;
import com.mgmtp.jfunk.core.step.base.StepMode;
import com.mgmtp.jfunk.data.DataSet;
import com.mgmtp.jfunk.web.WebConstants;
import com.mgmtp.jfunk.web.exception.InvalidValueException;

/**
 * Sets or checks a single HTML element using data from a {@link DataSet} or a fixed value. Several
 * element types are supported:
 * <ul>
 * <li>text fields</li>
 * <li>checkboxes</li>
 * <li>radio buttons</li>
 * <li>drop down lists</li>
 * </ul>
 * When not in {@link StepMode#SET_VALUE} the element is checked for a given value (
 * {@link StepMode#CHECK_VALUE} ) or checked for its default value ( {@link StepMode#CHECK_DEFAULT}
 * ).
 * 
 */
public class JFunkWebElement extends WebDriverStep {
    protected final By by;
    protected final String dataKey;
    protected final String dataSetKey;
    protected final ElementTrafo checkTrafo;
    protected final ElementTrafo setTrafo;
    protected final StepMode stepMode;

    private String elementValue;

    @Inject
    protected Map<String, DataSet> dataSets;

    /**
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param elementValue
     *            this value will be used to set or check the HTML element against
     */
    public JFunkWebElement(final By by, final String elementValue) {
        this(by, elementValue, StepMode.SET_VALUE);
    }

    /**
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param elementValue
     *            this value will be used to set or check the HTML element against
     * @param stepMode
     *            the desired {@link StepMode}
     */
    public JFunkWebElement(final By by, final String elementValue, final StepMode stepMode) {
        this(by, elementValue, stepMode, null, null);
    }

    /**
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param elementValue
     *            this value will be used to set or check the HTML element against
     * @param stepMode
     *            the desired {@link StepMode}
     * @param setTrafo
     *            when in {@link StepMode#SET_VALUE} this {@link ElementTrafo} will be applied at
     *            first to transform the value before it is set in the form
     * @param checkTrafo
     *            when in {@link StepMode#CHECK_VALUE} this {@link ElementTrafo} will be applied at
     *            first to transform the value before it is checked in the form
     */
    public JFunkWebElement(final By by, final String elementValue, final StepMode stepMode,
            final ElementTrafo setTrafo, final ElementTrafo checkTrafo) {
        this(by, elementValue, (Integer) null, (String) null, stepMode, setTrafo, checkTrafo);
    }

    /**
     * Creates a new instance of JFunkWebElement. This constructor offers the biggest variety of
     * parameters, several other constructors with fewer parameters are available.
     * 
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param dataKey
     *            When checking or setting the HTML element, this key is used to get the correct
     *            value from the given {@link DataSet} using {@link DataSet#getValue(String)}
     * @param dataSetKey
     *            the {@link DataSet} used for getting the value of the {@code dataKey}
     * @param stepMode
     *            the desired {@link StepMode}
     */
    public JFunkWebElement(final By by, final String dataKey, final String dataSetKey, final StepMode stepMode) {
        this(by, dataKey, null, dataSetKey, stepMode, null, null);
    }

    /**
     * Creates a new instance of JFunkWebElement. This constructor offers the biggest variety of
     * parameters, several other constructors with fewer parameters are available.
     * 
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param dataKey
     *            When checking or setting the HTML element, this key is used to get the correct
     *            value from the given {@link DataSet} using {@link DataSet#getValue(String)} or
     *            {@link DataSet#getValue(String, int)} if the specified index is non-null
     * @param index
     *            used to access indexed {@link DataSet} values
     * @param dataSetKey
     *            the {@link DataSet} used for getting the value of the {@code dataKey}
     * @param stepMode
     *            the desired {@link StepMode}
     */
    public JFunkWebElement(final By by, final String dataKey, final Integer index, final String dataSetKey,
            final StepMode stepMode) {
        this(by, dataKey, index, dataSetKey, stepMode, null, null);
    }

    /**
     * Creates a new instance of JFunkWebElement. This constructor offers the biggest variety of
     * parameters, several other constructors with fewer parameters are available.
     * 
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param dataKey
     *            When checking or setting the HTML element, this key is used to get the correct
     *            value from the given {@link DataSet} using {@link DataSet#getValue(String)}
     * @param dataSetKey
     *            the {@link DataSet} used for getting the value of the {@code dataKey}
     * @param stepMode
     *            the desired {@link StepMode}
     * @param setTrafo
     *            when in {@link StepMode#SET_VALUE} this {@link ElementTrafo} will be applied at
     *            first to transform the value before it is set in the form
     * @param checkTrafo
     *            when in {@link StepMode#CHECK_VALUE} this {@link ElementTrafo} will be applied at
     *            first to transform the value before it is checked in the form
     */
    public JFunkWebElement(final By by, final String dataKey, final String dataSetKey, final StepMode stepMode,
            final ElementTrafo setTrafo, final ElementTrafo checkTrafo) {
        this(by, dataKey, null, dataSetKey, stepMode, setTrafo, checkTrafo);
    }

    /**
     * Creates a new instance of JFunkWebElement. This constructor offers the biggest variety of
     * parameters, several other constructors with fewer parameters are available.
     * 
     * @param by
     *            By means of this value the HTML element is searched after which will then be
     *            checked or set.
     * @param dataKey
     *            When checking or setting the HTML element, this key is used to get the correct
     *            value from the given {@link DataSet} using {@link DataSet#getValue(String)} or
     *            {@link DataSet#getValue(String, int)} if the specified index is non-null
     * @param index
     *            used to access indexed {@link DataSet} values
     * @param dataSetKey
     *            the {@link DataSet} used for getting the value of the {@code dataKey}
     * @param stepMode
     *            the desired {@link StepMode}
     * @param setTrafo
     *            when in {@link StepMode#SET_VALUE} this {@link ElementTrafo} will be applied at
     *            first to transform the value before it is set in the form
     * @param checkTrafo
     *            when in {@link StepMode#CHECK_VALUE} this {@link ElementTrafo} will be applied at
     *            first to transform the value before it is checked in the form
     */
    public JFunkWebElement(final By by, final String dataKey, final Integer index, final String dataSetKey,
            final StepMode stepMode, final ElementTrafo setTrafo, final ElementTrafo checkTrafo) {
        this.by = by;
        this.dataKey = index == null ? dataKey : dataKey + JFunkConstants.INDEXED_KEY_SEPARATOR + index;
        this.dataSetKey = dataSetKey;
        this.stepMode = stepMode;
        this.setTrafo = setTrafo;
        this.checkTrafo = checkTrafo;
    }

    /**
     * @throws StepException
     *             <ul>
     *             <li>if element specified by {@link By} object in the constructor cannot be found</li>
     *             <li>if a validation error occurred while checking the value of the WebElement
     *             against the desired value</li>
     *             </ul>
     */
    @Override
    public void execute() throws StepException {
        elementValue = retrieveElementValue();

        final WebDriverWait wait = new WebDriverWait(getWebDriver(), WebConstants.DEFAULT_TIMEOUT);
        final WebElement element = wait.until(new Function<WebDriver, WebElement>() {

            @Override
            public WebElement apply(final WebDriver input) {
                List<WebElement> webElements = input.findElements(by);
                if (webElements.isEmpty()) {
                    throw new StepException("Could not find any matching element; By=" + by.toString());
                }
                /*
                 * If the search using the By object does find more than one matching element we are
                 * looping through all elements if we find at least one which matches the criteria
                 * below. If not, an exception is thrown.
                 */
                for (WebElement webElement : webElements) {
                    if (webElement.isDisplayed()) {
                        if (webElement.isEnabled() || !webElement.isEnabled()
                                && (stepMode == StepMode.CHECK_DEFAULT || stepMode == StepMode.CHECK_VALUE)) {
                            return webElement;
                        }
                    }
                }
                throw new StepException("All elements matching by=" + by + " were either invisible or disabled");
            }
        });

        switch (stepMode) {
        case CHECK_DEFAULT:
            // Check only for text input and textarea
            if (element.getTagName().equals(WebConstants.INPUT)
                    && element.getAttribute(WebConstants.TYPE).equals(WebConstants.TEXT)
                    || element.getTagName().equals(WebConstants.TEXTAREA)) {
                log.info(toString());
                String value = element.getAttribute(WebConstants.VALUE);
                if (!DataUtils.isDefaultValue(value)) {
                    throw new ValidationException("Wrong default value=" + value + " of " + this);
                }
            }
            break;
        case CHECK_VALUE:
            String checkValue = elementValue;
            if (checkTrafo != null) {
                checkValue = checkTrafo.trafo(checkValue);
            }

            log.info(this + ", checkValue=" + checkValue);
            if (element.getTagName().equalsIgnoreCase(WebConstants.SELECT)) {
                Select select = new Select(element);
                String value = select.getFirstSelectedOption().getAttribute(WebConstants.VALUE);
                if (!Objects.equal(checkValue, value)) {
                    String text = select.getFirstSelectedOption().getText();
                    if (!Objects.equal(checkValue, text)) {
                        throw new InvalidValueException(element, checkValue,
                                text + " (option value=" + value + ")");
                    }
                }
            } else if (WebConstants.INPUT.equalsIgnoreCase(element.getTagName())
                    && WebConstants.RADIO.equals(element.getAttribute(WebConstants.TYPE))) {
                List<WebElement> elements = getWebDriver().findElements(by);
                for (WebElement webElement : elements) {
                    if (webElement.isDisplayed() && webElement.isEnabled()) {
                        String elVal = webElement.getAttribute(WebConstants.VALUE);
                        if (elVal.equals(checkValue) && !webElement.isSelected()) {
                            throw new InvalidValueException(element, checkValue, elVal);
                        }
                    }
                }
            } else if (WebConstants.CHECKBOX.equals(element.getAttribute(WebConstants.TYPE))) {
                boolean elVal = element.isSelected();
                if (elVal != Boolean.valueOf(checkValue)) {
                    throw new InvalidValueException(element, checkValue, String.valueOf(elVal));
                }
            } else {
                String value = element.getAttribute(WebConstants.VALUE);
                if (!Objects.equal(checkValue, value)) {
                    throw new InvalidValueException(element, checkValue, value);
                }
            }
            break;
        case SET_VALUE:
            String setValue = elementValue;
            if (setTrafo != null) {
                setValue = setTrafo.trafo(setValue);
            }
            log.info(this + (setTrafo != null ? ", setValue (after trafo)=" + setValue : ""));
            if (element.getTagName().equalsIgnoreCase(WebConstants.SELECT)) {
                Select select = new Select(element);
                // First check if a matching value can be found
                List<WebElement> options = select.getOptions();
                boolean found = false;
                for (WebElement option : options) {
                    String optionValue = option.getAttribute(WebConstants.VALUE);
                    if (StringUtils.equals(optionValue, setValue)) {
                        /*
                         * WebElement with matching value could be found --> we are finished
                         */
                        found = true;
                        select.selectByValue(setValue);
                        break;
                    }
                }
                if (!found) {
                    /*
                     * Fallback: look for a WebElement with a matching visible text
                     */
                    for (WebElement option : options) {
                        String visibleText = option.getText();
                        if (StringUtils.equals(visibleText, setValue)) {
                            /*
                             * WebElement with matching value could be found --> we are finished
                             */
                            found = true;
                            select.selectByVisibleText(setValue);
                            break;
                        }
                    }
                }
                if (!found) {
                    throw new StepException(
                            "Could not find a matching option element in " + element + " , By: " + by.toString());
                }
            } else if (WebConstants.INPUT.equalsIgnoreCase(element.getTagName())
                    && WebConstants.RADIO.equals(element.getAttribute(WebConstants.TYPE))) {
                List<WebElement> elements = getWebDriver().findElements(by);
                for (WebElement webElement : elements) {
                    if (webElement.isDisplayed() && webElement.isEnabled()) {
                        String elVal = webElement.getAttribute(WebConstants.VALUE);
                        if (elVal.equals(setValue) && !webElement.isSelected()) {
                            webElement.click();
                        }
                    }
                }
            } else if (WebConstants.CHECKBOX.equals(element.getAttribute(WebConstants.TYPE))) {
                if (Boolean.valueOf(setValue) && !element.isSelected()
                        || !Boolean.valueOf(setValue) && element.isSelected()) {
                    element.click();
                }
            } else {
                if (element.isDisplayed() && element.isEnabled() && (element.getAttribute("readonly") == null
                        || element.getAttribute("readonly").equals("false"))) {
                    element.clear();
                    element.sendKeys(setValue);
                } else {
                    log.warn("Element is invisible or disabled, value cannot be set");
                }
            }
            break;
        case NONE:
            // do nothing
            break;
        default:
            throw new IllegalArgumentException("Unhandled StepMode=" + stepMode);
        }
    }

    protected String retrieveElementValue() {
        return (dataSetKey != null) ? dataSets.get(dataSetKey).getValue(dataKey) : dataKey;
    }

    /**
     * @return the dataSetKey
     */
    public String getDataSetKey() {
        return dataSetKey;
    }

    /**
     * @return the dataKey
     */
    public String getDataKey() {
        return dataKey;
    }

    /**
     * @return the by
     */
    public By getBy() {
        return by;
    }

    @Override
    public String toString() {
        ToStringBuilder tsb = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
        tsb.append("by", by);
        tsb.append("dataSetKey", dataSetKey);
        tsb.append("dataKey", dataKey);
        tsb.append("elementValue", elementValue);
        tsb.append("stepMode", stepMode);
        return tsb.toString();
    }
}