All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sdl.selenium.WebLocatorSuggestions Maven / Gradle / Ivy

Go to download

Automated Acceptance Testing. Selenium and Selenium WebDriver test framework for web applications. (optimized for dynamic html, ExtJS, Bootstrap, complex UI, simple web applications/sites)

The newest version!
package com.sdl.selenium;

import com.sdl.selenium.utils.config.WebLocatorConfig;
import com.sdl.selenium.web.SearchType;
import com.sdl.selenium.web.WebLocator;
import com.sdl.selenium.web.XPathBuilder;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.*;

public class WebLocatorSuggestions {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebLocatorSuggestions.class);

    private static boolean suggestAttributes = false;

    private static SearchType[] textGroup = {
            SearchType.EQUALS,
            SearchType.STARTS_WITH,
            SearchType.CONTAINS
    };
    private static SearchType[] childGroup = {
            SearchType.CHILD_NODE,
            SearchType.DEEP_CHILD_NODE_OR_SELF,
            SearchType.DEEP_CHILD_NODE,
            SearchType.CONTAINS_ALL,
            SearchType.CONTAINS_ALL_CHILD_NODES,
            SearchType.CONTAINS_ANY,
            SearchType.HTML_NODE
    };

    public static boolean isSuggestAttributes() {
        return suggestAttributes;
    }

    public static void setSuggestAttributes(boolean suggestAttributes) {
        WebLocatorSuggestions.suggestAttributes = suggestAttributes;
    }

    private static String getMatchedElementsHtml(WebLocator webLocator) {
        String result = "";

        for (WebElement element : webLocator.findElements()) {

            String outerHtml = element.getAttribute("outerHTML");
            String innerHtml = element.getAttribute("innerHTML");
            if (innerHtml.length() < 50) {
                result = result.concat(outerHtml).concat("\n");
            } else {
                result = result.concat(outerHtml.replace(innerHtml, "...")).concat("\n");
            }
        }

        return result.trim();
    }

    public static void getPageSuggestions(WebLocator view) {

        Map webLocatorMap = WebLocatorUtils.webLocatorAsMap(view);

        if (webLocatorMap.size() == 0) {
            getSuggestion(view);
        } else {
            getPageSuggestions(webLocatorMap);
        }
    }

    private static void getPageSuggestions(Map webLocatorMap) {
        boolean allElementsExist = true;

        for (String key : webLocatorMap.keySet()) {

            WebLocator webLocator = webLocatorMap.get(key);
            if (!webLocator.isPresent()) {
                allElementsExist = false;
                LOGGER.info("{} not found in page.", key);
                getPageSuggestions(webLocator);
            }
        }

        if (allElementsExist) {
            LOGGER.info("All elements are present in the page.");
        }
    }

    public static WebLocator getSuggestion(WebLocator webLocator) {
        LOGGER.debug("getSuggestion >> enter");
        WebLocator suggestion = getElementSuggestion(webLocator);
        LOGGER.debug("getSuggestion << exit");
        return suggestion;
    }

    public static void discoverElements(WebLocator originalWebLocator) {
        LOGGER.debug("Found elements: {}", WebLocatorUtils.discoverExtJs6Elements(originalWebLocator));
    }

    private static WebLocator getElementSuggestion(WebLocator originalWebLocator) {

        WebLocator webLocator = getClone(originalWebLocator);
        if (webLocator == null) {
            return null;
        }

        if (webLocator.currentElement != null || webLocator.isPresent()) {
            if (webLocator.currentElement.isDisplayed()) {
                LOGGER.debug("The element already exists: {}", WebLocatorUtils.discoverExtJs6Elements(webLocator));
            } else {
                LOGGER.info("The element already exists but it is not visible: {}", WebLocatorUtils.getHtmlTree(webLocator));
            }
            return webLocator;
        }

        WebLocator parent = webLocator.getPathBuilder().getContainer();
        if (parent != null && !parent.isPresent()) {
            LOGGER.warn("The container ({}) of this webLocator ({}) was not found.", parent.getXPath(), webLocator.getXPath());
            return null;
        }

        //if the WebLocator should have a label check that it actually exists and try to offer suggestions if it doesn't.
        if (webLocator.getPathBuilder().getLabel() != null) {
            WebLocator result = suggestLabelCorrections(webLocator);
            if (result != null) {
//                return getElementSuggestion(result);
                return result;
            }
        }

        SearchType[] solution = suggestTextSearchType(webLocator);
        if (solution != null) {
            LOGGER.warn("Found the element using text search type {}", Arrays.toString(solution));
            webLocator.setSearchTextType(solution);
            return webLocator;
        }

        solution = suggestTitleSearchType(webLocator);
        if (solution != null) {
            LOGGER.warn("Found the element using title search type {}", Arrays.toString(solution));
            webLocator.setSearchTitleType(solution);
            return webLocator;
        }

        if (isSuggestAttributes()) {
            WebLocator locator = suggestAttributeSubsets(webLocator);
            if (locator == null) {
                LOGGER.debug("No suggestions found for {}", originalWebLocator);
            }
            return locator;
        } else {
            LOGGER.debug("No suggestions found for {}", originalWebLocator);
            return null;
        }
    }

    public static WebLocator getClone(WebLocator originalWebLocator) {
        try {
            WebLocator webLocator = originalWebLocator.getClass().getDeclaredConstructor().newInstance();
            XPathBuilder builder = originalWebLocator.getPathBuilder().clone();
            webLocator.setPathBuilder(builder);
            return webLocator;
        } catch (CloneNotSupportedException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            LOGGER.error("Error while cloning the WebLocator: " + e.getMessage());
        }
        return null;
    }

    /**
     * @return True if everything is ok and the desired element was found, false otherwise.
     */
    private static WebLocator suggestLabelCorrections(WebLocator webLocator) {

        XPathBuilder xPathBuilder = webLocator.getPathBuilder();

        String label = xPathBuilder.getLabel();

        List searchTypes = xPathBuilder.getSearchLabelType();
        //this is the hardcoded value for the default search label type
        if (searchTypes.isEmpty()) {
            searchTypes.add(SearchType.EQUALS);
        }
        SearchType[] labelSearchTypes = new SearchType[searchTypes.size()];
        searchTypes.toArray(labelSearchTypes);

        WebLocator labelLocator = new WebLocator(xPathBuilder.getContainer())
                .setRender(Duration.ZERO)
                .setText(label, labelSearchTypes)
                .setTag(xPathBuilder.getLabelTag());

        if (labelLocator.isPresent()) {

            LOGGER.info("Found the label: {}", getMatchedElementsHtml(labelLocator));

            String tag = webLocator.getPathBuilder().getTag();

            WebLocator labelPosition = new WebLocator(labelLocator)
                    .setElPath(xPathBuilder.getLabelPosition() + tag);

            if (labelPosition.isPresent()) {
                LOGGER.info("'{}' elements found at the specified label position: {}", tag, getMatchedElementsHtml(labelPosition));
            } else {
                LOGGER.info("No '{}' elements found at the specified label position: {}", tag, xPathBuilder.getLabelPosition());

                labelPosition.setElPath(xPathBuilder.getLabelPosition() + "*");
                if (labelPosition.isPresent()) {
                    LOGGER.warn("All elements found at the specified label position: {}", getMatchedElementsHtml(labelPosition));
                    webLocator.setTag("*");
                    return webLocator;
                }
            }

        } else {
            LOGGER.info("Could not find the label '{}' using  tag '{}' and search type '{}'",
                    label, xPathBuilder.getLabelTag(), Arrays.toString(labelSearchTypes));

            SearchType[] solution = suggestTextSearchType(labelLocator);
            if (solution != null) {
                LOGGER.warn("But found it using search types {}", Arrays.toString(solution));
                webLocator.setLabel(label, solution);
                return webLocator;
            } else {
                labelLocator.setTag("*");
                if (labelLocator.isPresent()) {
                    LOGGER.warn("But found it using tag *.");
                    webLocator.setLabelTag("*");
                    return webLocator;
                } else {
                    SearchType[] solution2 = suggestTextSearchType(labelLocator);
                    if (solution2 != null) {
                        LOGGER.warn("But found it using tag * and search types {}", Arrays.toString(solution2));
                        webLocator.setLabel(label, solution2);
                        webLocator.setLabelTag("*");
                        return webLocator;
                    }
                }
            }
        }

        return null;
    }

    public static SearchType[] suggestTextSearchType(WebLocator webLocator) {

        for (SearchType textSearchType : textGroup) {

            SearchType[] solution1 = {textSearchType, SearchType.TRIM};
            webLocator.setSearchTextType(solution1);

            if (webLocator.isPresent()) {
                return solution1;
            }

            for (SearchType childSearchType : childGroup) {

                SearchType[] solution2 = {textSearchType, childSearchType, SearchType.TRIM};
                webLocator.setSearchTextType(solution2);

                if (webLocator.isPresent()) {
                    return solution2;
                }
            }
        }

        return null;
    }

    public static SearchType[] suggestTitleSearchType(WebLocator webLocator) {

        for (SearchType textSearchType : textGroup) {

            SearchType[] solution1 = {textSearchType, SearchType.TRIM};
            webLocator.setSearchTitleType(solution1);

            if (webLocator.isPresent()) {
                return solution1;
            }

            for (SearchType childSearchType : childGroup) {

                SearchType[] solution2 = {textSearchType, childSearchType, SearchType.TRIM};
                webLocator.setSearchTitleType(solution2);

                if (webLocator.isPresent()) {
                    return solution2;
                }
            }
        }

        return null;
    }

    private static WebLocator suggestAttributeSubsets(WebLocator view) {

        List nonNullFields = getNonNullFields(view.getPathBuilder());

        String[] originalFields = new String[nonNullFields.size()];
        nonNullFields.toArray(originalFields);

        int k = nonNullFields.size() - 1;

        while (k > 0) {
            try {
                proposeSolutions(originalFields, k, 0, new String[k], view);
            } catch (SolutionFoundException e) {
                return view;
            }
            k--;
        }

        return null;
    }

    private static List getNonNullFields(XPathBuilder builder) {

        String[] availableFieldsArray = {
                "id",
                "elPath",
                "baseCls",
                "cls",
                "name",
                "text",
                "style",
                "title",
                "type",
                "visibility",
                "root",
                "tag",
                "position",
                "resultIdx"
        };

        List availableFields = Arrays.asList(availableFieldsArray);

        //create a list of non null fields
        List nonNullFields = new LinkedList<>();

        for (Field f : builder.getClass().getDeclaredFields()) {

            if (availableFields.contains(f.getName())) {

                f.setAccessible(true);

                try {
                    Object value = f.get(builder);
                    if (value != null) {
                        nonNullFields.add(f.getName());
                    }
                } catch (IllegalAccessException e) {
                    //will not get here because of f.setAccessible(true);
                }
            }
        }
        return nonNullFields;
    }

    /**
     * Generates combinations of all objects from the dataSet taken k at a time.
     * Each combination is a possible solution that must be validated.
     */
    private static void proposeSolutions(String[] dataSet, int k, int startPosition, String[] result, WebLocator webLocator) throws SolutionFoundException {

        if (k == 0) {
            validateSolution(webLocator, differences(dataSet, result));
            return;
        }

        for (int i = startPosition; i <= dataSet.length - k; i++) {
            result[result.length - k] = dataSet[i];
            proposeSolutions(dataSet, k - 1, i + 1, result, webLocator);
        }
    }

    /**
     * Nullifies all fields of the builder that are not part of the proposed solution and validates the new xPath.
     */
    private static void validateSolution(WebLocator webLocator, String[] differences) throws SolutionFoundException {

        //all changes will be recorded here in a human readable form
        StringBuilder sb = new StringBuilder();

        //all changes will be kept here and restored after the xPath is generated.
        Map backup = new HashMap<>();

        XPathBuilder builder = webLocator.getPathBuilder();

        for (String fieldName : differences) {
            try {

                Field field = XPathBuilder.class.getDeclaredField(fieldName);
                field.setAccessible(true);

                Object fieldValue = field.get(builder);

                switch (fieldName) {
                    case "root":
                        field.set(builder, "//");
                        break;
                    case "tag":
                        field.set(builder, "*");
                        break;
                    case "visibility":
                        field.set(builder, false);
                        break;
                    case "labelTag":
                        field.set(builder, "label");
                        break;
                    case "labelPosition":
                        field.set(builder, WebLocatorConfig.getDefaultLabelPosition());
                        break;
                    case "position":
                    case "resultIdx":
                        field.set(builder, -1);
                        break;
                    default:
                        field.set(builder, null);
                }

                backup.put(fieldName, fieldValue);
                sb.append(String.format("set%s(\"%s\") and ", capitalize(fieldName), fieldValue));

            } catch (IllegalAccessException | NoSuchFieldException e) {
                e.printStackTrace();
                //this is not a valid solution. nothing to do here.
            }
        }

        int matches = webLocator.size();

        if (matches > 0) {

            //remove last 'and' from the message
            if (sb.lastIndexOf("and") > 0) {
                sb.replace(sb.lastIndexOf("and"), sb.length(), "");
            }

            LOGGER.warn("Found {} matches by removing {}: \n{}", matches, sb.toString(), getMatchedElementsHtml(webLocator));
            throw new SolutionFoundException();
        }

        //restore the original builder from the backup
        for (String fieldName : differences) {
            try {

                Field field = XPathBuilder.class.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(builder, backup.get(fieldName));

            } catch (IllegalAccessException | NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }

    public static class SolutionFoundException extends Exception {
    }

    /**
     * Returns an array that contains all differences between first and second.
     */
    private static String[] differences1(String[] first, String[] second) {
        List differences = new ArrayList<>(Arrays.asList(first));
        differences.removeAll(Arrays.asList(second));
        return differences.toArray(String[]::new);
    }

    private static String[] differences(String[] first, String[] second) {

        String[] sortedFirst = Arrays.copyOf(first, first.length); // O(n)
        String[] sortedSecond = Arrays.copyOf(second, second.length); // O(m)
        Arrays.sort(sortedFirst); // O(n log n)
        Arrays.sort(sortedSecond); // O(m log m)

        int firstIndex = 0;
        int secondIndex = 0;

        LinkedList diffs = new LinkedList<>();

        while (firstIndex < sortedFirst.length && secondIndex < sortedSecond.length) {
            int compare = (int) Math.signum(sortedFirst[firstIndex].compareTo(sortedSecond[secondIndex]));

            switch (compare) {
                case -1:
                    diffs.add(sortedFirst[firstIndex]);
                    firstIndex++;
                    break;
                case 1:
                    diffs.add(sortedSecond[secondIndex]);
                    secondIndex++;
                    break;
                default:
                    firstIndex++;
                    secondIndex++;
            }
        }

        String[] strDups = new String[diffs.size()];

        return diffs.toArray(strDups);
    }

    private static String capitalize(final String word) {
        return Character.toUpperCase(word.charAt(0)) + word.substring(1);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy