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

com.sdl.selenium.web.XPathBuilder 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.web;

import com.google.common.base.Strings;
import com.sdl.selenium.utils.config.WebDriverConfig;
import com.sdl.selenium.utils.config.WebLocatorConfig;
import com.sdl.selenium.web.table.AbstractCell;
import com.sdl.selenium.web.utils.Utils;
import org.apache.commons.collections4.CollectionUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;

import java.time.Duration;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * This class is used to simple construct xpath for WebLocator's
 */
public class XPathBuilder implements Cloneable {
    private static final Logger log = org.slf4j.LoggerFactory.getLogger(XPathBuilder.class);
    public List defaultSearchTextType = new ArrayList<>();
    private String className = "WebLocator";
    private String root = "//";
    private String tag = "*";
    private String id;
    private String elPath;
    private String elCssSelector;
    private String baseCls;
    private String cls;
    private List classes;
    private Operator operator = Operator.AND;
    private List excludeClasses;
    private String name;
    private String text;
    private List searchTextType = WebLocatorConfig.getSearchTextType();
    private List searchTitleType = new ArrayList<>();
    private List searchLabelType = new ArrayList<>();
    private String style;
    private String title;
    private Map templates = new LinkedHashMap<>();
    private Map templateTitle = new LinkedHashMap<>();
    private Map templatesValues = new LinkedHashMap<>();
    private Map elPathSuffix = new LinkedHashMap<>();

    private String infoMessage;

    private String label;
    private String labelTag = "label";
    private String labelPosition = WebLocatorConfig.getDefaultLabelPosition();

    private String position;
    private String resultIdx;
    private String type;
    private String localName;
    private Map attribute = new LinkedHashMap<>();
    private Map attributes = new LinkedHashMap<>();
    private Operator attributesOperator = Operator.AND;

    //private int elIndex; // TODO try to find how can be used

    private boolean visibility;
    private long renderMillis = WebLocatorConfig.getDefaultRenderMillis();
    private Duration render = Duration.ofMillis(WebLocatorConfig.getDefaultRenderMillis());
    private int activateSeconds = 60;
    private Duration activate = Duration.ofSeconds(60);

    private WebLocator container;
    private SearchContext shadowRoot;
    private WebElement webElement;
    private ChildNodes childNodes = new ChildNodes();
    private String finalXPath;

    protected XPathBuilder() {
        setTemplate("visibility", "count(ancestor-or-self::*[contains(@style, 'display: none')]) = 0");
        setTemplate("style", "contains(@style, '%s')");
        setTemplate("id", "@id='%s'");
        setTemplate("position", "[position() = %s]");
        setTemplate("tagAndPosition", "%1$s[%2$s]");
        setTemplate("name", "@name='%s'");
        setTemplate("localName", "name()='%s'");
        setTemplate("class", "contains(concat(' ', @class, ' '), ' %s ')");
        setTemplate("excludeClass", "not(contains(@class, '%s'))");
        setTemplate("cls", "@class='%s'");
        setTemplate("type", "@type='%s'");
        setTemplate("title", "@title='%s'");
        setTemplate("titleEl", "count(.%s) > 0");
        setTemplate("DEEP_CHILD_NODE_OR_SELF", "(%1$s or count(*//text()[%1$s]) > 0)");
        setTemplate("DEEP_CHILD_NODE", "count(*//text()[%s]) > 0");
        setTemplate("CHILD_NODE", "count(text()[%s]) > 0");
        setTemplate("HTML_NODE", "%s");
        setTemplate("HTML_NODES", "(normalize-space(concat(./*[1]//text(), ' ', text()[1], ' ', ./*[2]//text(), ' ', text()[2], ' ', ./*[3]//text(), ' ', text()[3], ' ', ./*[4]//text(), ' ', text()[4], ' ', ./*[5]//text(), ' ', text()[5]))=%1$s or normalize-space(concat(text()[1], ' ', ./*[1]//text(), ' ', text()[2], ' ', ./*[2]//text(), ' ', text()[3], ' ', ./*[3]//text(), ' ', text()[4], ' ', ./*[4]//text(), ' ', text()[5], ' ', ./*[5]//text()))=%1$s)");
        setTemplate("childNodes", "count(.%s) > 0");
        setTemplate("attributes", "contains(concat(' ', @%1$s, ' '), ' %2$s ')");
    }

    // =========================================
    // ========== setters & getters ============
    // =========================================

    /**
     * 

Used for finding element process (to generate xpath address)

* * @param root If the path starts with // then all elements in the document which fulfill following criteria are selected. eg. // or / * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setRoot(final String root) { this.root = root; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param tag (type of DOM element) eg. input or h2 * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setTag(final String tag) { this.tag = tag; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param id eg. id="buttonSubmit" * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setId(final String id) { this.id = id; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* Once used all other attributes will be ignored. Try using this class to a minimum! * * @param elPath absolute way (xpath) to identify element * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setElPath(final String elPath) { this.elPath = elPath; return (T) this; } /** *

Used for finding element process (to generate css selectors address)

* Once used all other attributes will be ignored. Try using this class to a minimum! * * @param elCssSelector absolute way (css selectors) to identify element * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setElCssSelector(final String elCssSelector) { this.elCssSelector = elCssSelector; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param baseCls base class * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setBaseCls(final String baseCls) { this.baseCls = baseCls; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Find element with exact math of specified class (equals)

* * @param cls class of element * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setCls(final String cls) { this.cls = cls; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Use it when element must have all specified css classes (order is not important).

*

Example:

*
     *     WebLocator element = new WebLocator().setClasses("bg-btn", "new-btn");
     * 
*
    *
  • Provided classes must be conform css rules.
  • *
* * @param classes list of classes * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setClasses(final String... classes) { if (classes != null) { this.classes = Arrays.asList(classes); } return (T) this; } @SuppressWarnings("unchecked") public T setClasses(Operator operator, final String... classes) { if (classes != null) { this.operator = operator; this.classes = Arrays.asList(classes); } return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param excludeClasses list of class to be excluded * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setExcludeClasses(final String... excludeClasses) { if (excludeClasses != null) { this.excludeClasses = Arrays.asList(excludeClasses); } return (T) this; } @SuppressWarnings("unchecked") public T setChildNodes(SearchType searchType, final WebLocator... childNodes) { if (searchType == null || searchType == SearchType.EQUALS || searchType == SearchType.CONTAINS) { if (childNodes != null) { List children = Stream.of(childNodes).filter(Objects::nonNull).collect(Collectors.toList()); this.childNodes.setChildNodes(children); if (this.childNodes.getSearchType() == null) { this.childNodes.setSearchType(searchType); } } return (T) this; } else { throw new IllegalStateException("Support only: EQUALS or CONTAINS!"); } } /** *

Used for finding element process (to generate xpath address)

* * @param name eg. name="buttonSubmit" * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setName(final String name) { this.name = name; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param localName eg. name()="svg" * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setLocalName(final String localName) { this.localName = localName; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param text with which to identify the item * @param searchTypes type search text element: see more details see {@link SearchType} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setText(final String text, final SearchType... searchTypes) { this.text = text; // notSupportedForCss(text, "text"); // if(text == null) { // xpath.remove("text"); // } else { // xpath.add("text"); // } setSearchTextType(searchTypes); return (T) this; } /** *

Used for finding element process (to generate xpath address)

* This method reset searchTextTypes and set to new searchTextTypes. * * @param searchTextTypes accepted values are: {@link SearchType#EQUALS} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setSearchTextType(SearchType... searchTextTypes) { if (searchTextTypes == null) { this.searchTextType = WebLocatorConfig.getSearchTextType(); } else { this.searchTextType.addAll(0, Arrays.asList(searchTextTypes)); } this.searchTextType.addAll(defaultSearchTextType); this.searchTextType = cleanUpSearchType(this.searchTextType); return (T) this; } /** *

Used for finding element process (to generate xpath address)

* This method add new searchTextTypes to existing searchTextTypes. * * @param searchTextTypes accepted values are: {@link SearchType#EQUALS} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T addSearchTextType(SearchType... searchTextTypes) { if (searchTextTypes != null) { this.searchTextType.addAll(0, Arrays.asList(searchTextTypes)); } this.searchTextType = cleanUpSearchType(this.searchTextType); return (T) this; } protected List cleanUpSearchType(List searchTextTypes) { List searchTypes = searchTextTypes; if (searchTextTypes.size() > 1) { Set unique = new HashSet<>(); searchTypes = searchTextTypes.stream() .filter(c -> unique.add(c.getGroup())) .collect(Collectors.toList()); } return searchTypes; } /** *

Used for finding element process (to generate xpath address)

* * @param searchLabelTypes accepted values are: {@link SearchType} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setSearchLabelType(SearchType... searchLabelTypes) { this.searchLabelType = new ArrayList<>(); if (searchLabelTypes != null) { this.searchLabelType.addAll(0, Arrays.asList(searchLabelTypes)); } this.searchLabelType = cleanUpSearchType(this.searchLabelType); return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param style of element * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setStyle(final String style) { this.style = style; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Title only applies to Panel, and if you set the item "setTemplate("title", "@title='%s'")" a template.

* * @param title of element * @param searchTypes see {@link SearchType} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setTitle(final String title, final SearchType... searchTypes) { this.title = title; if (searchTypes != null && searchTypes.length > 0) { setSearchTitleType(searchTypes); } else { this.searchTitleType.addAll(defaultSearchTextType); } return (T) this; } @SuppressWarnings("unchecked") public T setSearchTitleType(SearchType... searchTitleTypes) { if (searchTitleTypes == null) { this.searchTitleType = WebLocatorConfig.getSearchTextType(); } else { this.searchTitleType.addAll(0, Arrays.asList(searchTitleTypes)); } this.searchTitleType.addAll(defaultSearchTextType); this.searchTitleType = cleanUpSearchType(this.searchTitleType); return (T) this; } @SuppressWarnings("unchecked") public T setTemplateTitle(WebLocator titleEl) { if (titleEl == null) { templateTitle.remove("title"); } else { templateTitle.put("title", titleEl); setSearchTitleType(titleEl.getPathBuilder().getSearchTitleType().toArray(new SearchType[0])); } return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Example:

*
     *     TODO
     * 
* * @param key suffix key * @param elPathSuffix additional identification xpath element that will be added at the end * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setElPathSuffix(final String key, final String elPathSuffix) { if (Strings.isNullOrEmpty(elPathSuffix)) { this.elPathSuffix.remove(key); } else { this.elPathSuffix.put(key, elPathSuffix); } return (T) this; } public Map getTemplatesValues() { return templatesValues; } /** *

Used for finding element process (to generate xpath address)

*

Example:

*
     *     TODO
     * 
* * @param key template * @param value template * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setTemplateValue(final String key, final String... value) { if (value == null) { this.templatesValues.remove(key); } else { this.templatesValues.put(key, value); } return (T) this; } /** * For customize template please see here: See http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#dpos * * @param key name template * @param value template * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setTemplate(final String key, final String value) { if (value == null) { templates.remove(key); } else { templates.put(key, value); } return (T) this; } @SuppressWarnings("unchecked") public T addToTemplate(final String key, final String value) { String template = getTemplate(key); if (!Strings.isNullOrEmpty(template)) { template += " and "; } else { template = ""; } setTemplate(key, template + value); return (T) this; } public String getTemplate(final String key) { return templates.get(key); } /** *

Used in logging process

* * @param infoMessage info Message * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setInfoMessage(final String infoMessage) { this.infoMessage = infoMessage; return (T) this; } @SuppressWarnings("unchecked") public T setVisibility(final boolean visibility) { this.visibility = visibility; return (T) this; } @SuppressWarnings("unchecked") @Deprecated public T setRenderMillis(final long renderMillis) { this.renderMillis = renderMillis; return (T) this; } @SuppressWarnings("unchecked") public T setRender(Duration duration) { this.render = duration; return (T) this; } @SuppressWarnings("unchecked") @Deprecated public T setActivateSeconds(final int activateSeconds) { this.activateSeconds = activateSeconds; return (T) this; } @SuppressWarnings("unchecked") public T setActivate(Duration duration) { this.activate = duration; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param container parent containing element. * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setContainer(WebLocator container) { this.container = container; if (container != null) { WebElement webElement = container.getPathBuilder().getWebElement(); if (webElement != null) { this.webElement = webElement; } SearchContext shadowRoot = container.getPathBuilder().getShadowRoot(); if (shadowRoot != null) { this.shadowRoot = shadowRoot; } } return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param webElement parent containing element. * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setWebElement(WebElement webElement) { this.webElement = webElement; this.root = ""; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param shadowRoot parent containing element. * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setShadowRoot(SearchContext shadowRoot) { this.shadowRoot = shadowRoot; this.root = ""; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param label text label element * @param searchTypes type search text element: see more details see {@link SearchType} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setLabel(final String label, final SearchType... searchTypes) { this.label = label; if (searchTypes != null && searchTypes.length > 0) { setSearchLabelType(searchTypes); } return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param labelTag label tag element * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setLabelTag(final String labelTag) { this.labelTag = labelTag; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @param labelPosition position of this element reported to label * @param the element which calls this method * @return this element * @see http://www.w3schools.com/xpath/xpath_axes.asp" */ @SuppressWarnings("unchecked") public T setLabelPosition(final String labelPosition) { this.labelPosition = labelPosition; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     //*[contains(@class, 'x-grid-panel')][position() = 1]
     * 
* * @param position starting index = 1 * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setPosition(final int position) { this.position = position + ""; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     //*[contains(@class, 'x-grid-panel')][last()]
     * 
* * @param position {@link Position} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setPosition(final Position position) { this.position = position.getValue(); return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     (//*[contains(@class, 'x-grid-panel')])[1]
     * 
* More details please see: http://stackoverflow.com/questions/4961349/combine-xpath-predicate-with-position * * @param resultIdx starting index = 1 * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setResultIdx(final int resultIdx) { this.resultIdx = resultIdx + ""; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     (//*[contains(@class, 'x-grid-panel')])[last()]
     * 
* More details please see: http://stackoverflow.com/questions/4961349/combine-xpath-predicate-with-position * * @param resultIdx {@link Position} * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setResultIdx(final Position resultIdx) { this.resultIdx = resultIdx.getValue(); return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     //*[@type='type']
     * 
* * @param type eg. 'checkbox' or 'button' * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setType(final String type) { this.type = type; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     //*[@placeholder='Search']
     * 
* * @param attribute eg. placeholder * @param value eg. Search * @param searchTypes accept only SearchType.EQUALS, SearchType.CONTAINS, SearchType.STARTS_WITH, SearchType.TRIM * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setAttribute(final String attribute, String value, final SearchType... searchTypes) { if (attribute != null) { if (value == null) { this.attribute.remove(attribute); } else { this.attribute.put(attribute, new SearchText(value, searchTypes)); } } return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     new WebLocator().setAttributes("placeholder", "Search", "Text");
     *     //*[contains(concat(' ', @placeholder, ' '), ' Search ') and contains(concat(' ', @placeholder, ' '), ' Text ')]
     * 
* * @param attribute eg. placeholder * @param values eg. Search,Text * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setAttributes(final String attribute, SearchText... values) { if (attribute != null) { if (values == null) { this.attributes.remove(attribute); } else { this.attributes.put(attribute, values); } } return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     new WebLocator().setAttributes("placeholder", Operator.OR, "Search", "Search");
     *     //*[contains(concat(' ', @placeholder, ' '), ' Search ') or contains(concat(' ', @placeholder, ' '), ' Search ')]
     * 
* * @param attribute eg. placeholder * @param operator eg. AND or OR * @param values eg. Search,Text * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setAttributes(final String attribute, Operator operator, SearchText... values) { if (attribute != null) { if (values == null) { this.attributes.remove(attribute); } else { this.attributes.put(attribute, values); this.attributesOperator = operator; } } return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Result Example:

*
     *     new WebLocator().setAttribute("atr", "2").setFinalXPath("//tab[1]")
     *     //*[@atr='2']//tab[1]
     * 
* * @param finalXPath //tab[1] * @param the element which calls this method * @return this element */ @SuppressWarnings("unchecked") public T setFinalXPath(final String finalXPath) { this.finalXPath = finalXPath; return (T) this; } // ========================================= // =============== Methods ================= // ========================================= protected void setClassName(final String className) { this.className = className; } protected boolean hasId() { return !Strings.isNullOrEmpty(id); } protected boolean hasCls() { return !Strings.isNullOrEmpty(cls); } protected boolean hasClasses() { return classes != null && !classes.isEmpty(); } protected boolean hasChildNodes() { return childNodes != null && !childNodes.getChildNodes().isEmpty(); } protected boolean hasExcludeClasses() { return excludeClasses != null && !excludeClasses.isEmpty(); } protected boolean hasBaseCls() { return !Strings.isNullOrEmpty(baseCls); } protected boolean hasName() { return !Strings.isNullOrEmpty(name); } protected boolean hasLocalName() { return !Strings.isNullOrEmpty(localName); } protected boolean hasText() { return !Strings.isNullOrEmpty(text); } protected boolean hasStyle() { return !Strings.isNullOrEmpty(style); } protected boolean hasElPath() { return !Strings.isNullOrEmpty(elPath); } protected boolean hasTag() { return tag != null && !"*".equals(tag); } protected boolean hasLabel() { return !Strings.isNullOrEmpty(label); } protected boolean hasTitle() { return !Strings.isNullOrEmpty(title) || !templateTitle.isEmpty(); } protected boolean hasPosition() { int anInt; try { anInt = Integer.parseInt(position); } catch (NumberFormatException e) { anInt = 1; } return !Strings.isNullOrEmpty(position) && anInt > 0; } protected boolean hasResultIdx() { int anInt; try { anInt = Integer.parseInt(resultIdx); } catch (NumberFormatException e) { anInt = 1; } return !Strings.isNullOrEmpty(resultIdx) && anInt > 0; } protected boolean hasType() { return !Strings.isNullOrEmpty(type); } protected boolean hasFinalXPath() { return !Strings.isNullOrEmpty(finalXPath); } // ========================================= // ============ XPath Methods ============== // ========================================= /** * Containing baseCls, class, name and style * * @return baseSelector */ protected String getBasePathSelector() { // TODO use disabled // TODO verify what need to be equal OR contains List selectors = new ArrayList<>(); CollectionUtils.addIgnoreNull(selectors, getBasePath()); if (!WebDriverConfig.isIE()) { if (hasStyle()) { selectors.add(applyTemplate("style", getStyle())); } // TODO make specific for WebLocator if (isVisibility()) { // TODO selectors.append(" and count(ancestor-or-self::*[contains(replace(@style, '\s*:\s*', ':'), 'display:none')]) = 0"); CollectionUtils.addIgnoreNull(selectors, applyTemplate("visibility", isVisibility())); } } return selectors.isEmpty() ? "" : String.join(" and ", selectors); } public String getBasePath() { List selectors = new ArrayList<>(); if (hasId()) { selectors.add(applyTemplate("id", getId())); } if (hasName()) { selectors.add(applyTemplate("name", getName())); } if (hasLocalName()) { selectors.add(applyTemplate("localName", getLocalName())); } if (hasBaseCls()) { selectors.add(applyTemplate("class", getBaseCls())); } if (hasCls()) { selectors.add(applyTemplate("cls", getCls())); } if (hasClasses()) { if (operator.name().equalsIgnoreCase("and")) { selectors.addAll(getClasses().stream().map(cls -> applyTemplate("class", cls)).collect(Collectors.toList())); } else { List collect = getClasses().stream().map(cls -> applyTemplate("class", cls)).collect(Collectors.toList()); String classes = String.join(" or ", collect); if (collect.size() > 1) { classes = "(" + classes + ")"; } selectors.add(classes); } } if (hasExcludeClasses()) { selectors.addAll(getExcludeClasses().stream().map(excludeClass -> applyTemplate("excludeClass", excludeClass)).collect(Collectors.toList())); } if (hasTitle()) { String title = getTitle(); WebLocator titleTplEl = templateTitle.get("title"); if (!Strings.isNullOrEmpty(title) || titleTplEl != null) { if (titleTplEl != null) { if (!Strings.isNullOrEmpty(title)) { titleTplEl.setText(title, searchTitleType.toArray(new SearchType[0])); } if (titleTplEl.getPathBuilder().getText() != null) { addTemplate(selectors, "titleEl", titleTplEl.getXPath()); } } else if (searchTitleType.isEmpty()) { addTemplate(selectors, "title", title); } else { addTextInPath(selectors, title, "@title", searchTitleType); } } } if (hasType()) { addTemplate(selectors, "type", getType()); } if (!attribute.isEmpty()) { for (Map.Entry entry : attribute.entrySet()) { List searchType = entry.getValue().getSearchTypes(); String text = entry.getValue().getValue(); addTextInPath(selectors, text, "@" + entry.getKey(), searchType); } } if (!attributes.isEmpty()) { for (Map.Entry entry : attributes.entrySet()) { SearchText[] values = entry.getValue(); if (attributesOperator.name().equalsIgnoreCase("and")) { for (SearchText searchText : values) { String text = searchText.getValue(); List searchType = searchText.getSearchTypes(); addTextInPath(selectors, text, "@" + entry.getKey(), searchType); } } else { List selectorsTmp = new ArrayList<>(); for (SearchText searchText : values) { String text = searchText.getValue(); List searchType = searchText.getSearchTypes(); addTextInPath(selectorsTmp, text, "@" + entry.getKey(), searchType); } String attributesPath = String.join(" or ", selectorsTmp); if (selectorsTmp.size() > 1) { attributesPath = "(" + attributesPath + ")"; } selectors.add(attributesPath); } } } if (hasText()) { if (!Strings.isNullOrEmpty(getTemplate("text"))) { selectors.add(applyTemplate("text", Utils.getEscapeQuotesText(getText()))); } else { addTextInPath(selectors, getText(), ".", searchTextType); } } for (Map.Entry entry : getTemplatesValues().entrySet()) { if (!"tagAndPosition".equals(entry.getKey())) { addTemplate(selectors, entry.getKey(), entry.getValue()); } } selectors.addAll(new ArrayList<>(elPathSuffix.values())); selectors.addAll(getChildNodesToSelector()); return selectors.isEmpty() ? null : String.join(" and ", selectors); } public void addTextInPath(List selectors, String text, String pattern, List searchTextType) { text = getTextAfterEscapeQuotes(text, searchTextType); boolean hasContainsAll = searchTextType.contains(SearchType.CONTAINS_ALL) || searchTextType.contains(SearchType.CONTAINS_ALL_CHILD_NODES); if (searchTextType.contains(SearchType.HTML_NODE)) { String selfPath = getTextWithSearchType(searchTextType, text, "."); addTemplate(selectors, "HTML_NODE", selfPath); } else if (searchTextType.contains(SearchType.HTML_NODES)) { addTemplate(selectors, "HTML_NODES", text); } else if (hasContainsAll || searchTextType.contains(SearchType.CONTAINS_ANY)) { String splitChar = String.valueOf(text.charAt(0)); String[] strings = Pattern.compile(Pattern.quote(splitChar)).split(text.substring(1)); for (int i = 0; i < strings.length; i++) { String escapeQuotesText = Utils.getEscapeQuotesText(strings[i]); if (searchTextType.contains(SearchType.CONTAINS_ALL_CHILD_NODES)) { if (searchTextType.contains(SearchType.CASE_INSENSITIVE)) { strings[i] = "count(*//text()[contains(translate(.," + escapeQuotesText.toUpperCase().replaceAll("CONCAT\\(", "concat(") + "," + escapeQuotesText.toLowerCase() + ")," + escapeQuotesText.toLowerCase() + ")]) > 0"; } else { strings[i] = "count(*//text()[contains(.," + escapeQuotesText + ")]) > 0"; } } else { String selfPath = getTextWithSearchType(searchTextType, escapeQuotesText, pattern); if (searchTextType.contains(SearchType.DEEP_CHILD_NODE_OR_SELF)) { strings[i] = applyTemplate("DEEP_CHILD_NODE_OR_SELF", selfPath); } else if (searchTextType.contains(SearchType.DEEP_CHILD_NODE)) { strings[i] = applyTemplate("DEEP_CHILD_NODE", selfPath); } else { strings[i] = "contains(" + (".".equals(pattern) ? "." : pattern) + "," + escapeQuotesText + ")"; } } } selectors.add(hasContainsAll ? String.join(" and ", strings) : "(" + String.join(" or ", strings) + ")"); } else if (searchTextType.contains(SearchType.DEEP_CHILD_NODE_OR_SELF)) { String selfPath = getTextWithSearchType(searchTextType, text, pattern); addTemplate(selectors, "DEEP_CHILD_NODE_OR_SELF", selfPath); } else if (searchTextType.contains(SearchType.DEEP_CHILD_NODE)) { String selfPath = getTextWithSearchType(searchTextType, text, pattern); addTemplate(selectors, "DEEP_CHILD_NODE", selfPath); } else if (searchTextType.contains(SearchType.CHILD_NODE)) { String selfPath = getTextWithSearchType(searchTextType, text, pattern); addTemplate(selectors, "CHILD_NODE", selfPath); } else { selectors.add(getTextWithSearchType(searchTextType, text, ".".equals(pattern) ? "text()" : pattern)); } } private List getChildNodesToSelector() { List selectors = new ArrayList<>(); if (hasChildNodes()) { List collect = getChildNodes().stream().map(this::getChildNodeSelector).collect(Collectors.toList()); boolean equals = this.childNodes.getSearchType() == null || this.childNodes.getSearchType() == SearchType.EQUALS; String children = collect.isEmpty() ? null : String.join(equals ? " and " : " or ", collect); selectors.add(children); } return selectors; } private String getChildNodeSelector(WebLocator child) { WebLocator breakElement = null; WebLocator childIterator = child; WebLocator parentElement = null; // break parent tree if is necessary while (childIterator.getPathBuilder().getContainer() != null && breakElement == null) { WebLocator parentElementIterator = childIterator.getPathBuilder().getContainer(); // child element has myself as parent if (parentElementIterator.getPathBuilder() == this) { childIterator.setContainer((WebLocator) null); // break parent tree while generating child address parentElement = parentElementIterator; breakElement = childIterator; } else { childIterator = parentElementIterator; } } String selector = applyTemplate("childNodes", child.getXPath()); if (breakElement != null) { breakElement.setContainer(parentElement); } return selector; } private void addTemplate(List selectors, String key, Object... arguments) { String tpl = applyTemplate(key, arguments); if (!Strings.isNullOrEmpty(tpl)) { selectors.add(tpl); } } protected String applyTemplate(String key, Object... arguments) { String tpl = templates.get(key); if (!Strings.isNullOrEmpty(tpl)) { return String.format(tpl, arguments); } return null; } private String applyTemplateValue(String key) { return applyTemplate(key, getTemplate(key)); } /** * this method is meant to be overridden by each component * * @param disabled disabled * @return String */ protected String getItemPath(boolean disabled) { String selector = getBaseItemPath(); String subPath = applyTemplateValue(disabled ? "disabled" : "enabled"); if (subPath != null) { selector += !Strings.isNullOrEmpty(selector) ? " and " + subPath : subPath; } Map templatesValues = getTemplatesValues(); String[] tagAndPositions = templatesValues.get("tagAndPosition"); List tagAndPosition = new ArrayList<>(); if (tagAndPositions != null) { tagAndPosition.addAll(Arrays.asList(tagAndPositions)); tagAndPosition.add(0, getTag()); } String tag; if (!tagAndPosition.isEmpty()) { tag = applyTemplate("tagAndPosition", tagAndPosition.toArray()); } else { tag = getTag(); } selector = getRoot() + tag + (!Strings.isNullOrEmpty(selector) ? "[" + selector + "]" : ""); return selector; } private String getTextWithSearchType(List searchType, String text, String pattern) { if (searchType.contains(SearchType.TRIM)) { pattern = "normalize-space(" + pattern + ")"; } if (searchType.contains(SearchType.CASE_INSENSITIVE)) { pattern = "translate(" + pattern + "," + text.toUpperCase().replaceAll("CONCAT\\(", "concat(") + "," + text.toLowerCase() + ")"; text = text.toLowerCase(); } if (searchType.contains(SearchType.EQUALS)) { if (Strings.isNullOrEmpty(text)) { text = pattern; } else { text = pattern + "=" + text; } } else if (searchType.contains(SearchType.STARTS_WITH)) { text = "starts-with(" + pattern + "," + text + ")"; } else { text = "contains(" + pattern + "," + text + ")"; } if (searchType.contains(SearchType.NOT)) { text = "not(" + text + ")"; } return text; } private String getTextAfterEscapeQuotes(String text, List searchType) { if (searchType.contains(SearchType.CONTAINS_ALL) || searchType.contains(SearchType.CONTAINS_ANY) || searchType.contains(SearchType.CONTAINS_ALL_CHILD_NODES)) { return text; } return Utils.getEscapeQuotesText(text); } private String getBaseItemPath() { return getBasePathSelector(); } private String getItemCssSelector() { List selectors = new ArrayList<>(); if (hasTag()) { selectors.add(getTag()); } if (hasId()) { selectors.add("#" + getId()); } if (hasBaseCls()) { selectors.add("." + getBaseCls()); } if (hasCls()) { selectors.add("[class=" + getCls() + "]"); } if (hasClasses()) { selectors.addAll(getClasses().stream().map(cls -> "." + cls).toList()); } if (hasExcludeClasses()) { // LOGGER.warn("excludeClasses is not supported yet"); selectors.addAll(getExcludeClasses().stream().map(excludeClass -> ":not(." + excludeClass + ")").toList()); } if (hasName()) { selectors.add("[name='" + getName() + "']"); } if (hasType()) { selectors.add("[type='" + getType() + "']"); } if (!attribute.isEmpty()) { selectors.addAll(attribute.entrySet().stream() .map(e -> "[" + e.getKey() + "='" + e.getValue().getValue() + "']") .toList()); } // for (Map.Entry entry : getTemplatesValues().entrySet()) { // addTemplate(selector, entry.getKey(), entry.getValue()); // } // for (String suffix : elPathSuffix.values()) { // selector.add(suffix); // } return selectors.isEmpty() ? "*" : String.join("", selectors); } public final By getSelector() { String cssSelector = getCssSelector(); return !Strings.isNullOrEmpty(cssSelector) ? By.cssSelector(cssSelector) : By.xpath(getXPath()); } private boolean isCssSelectorSupported() { return !(hasText() || hasElPath() || hasChildNodes() || hasStyle() || hasLabel() || hasTitle() || hasResultIdx()); } public final String getCssSelector() { String cssSelector = null; cssSelector = getElCssSelector(); if (WebLocatorConfig.isGenerateCssSelector()) { if (Strings.isNullOrEmpty(cssSelector)) { if (isCssSelectorSupported()) { cssSelector = getItemCssSelector(); if (hasPosition()) { if ("first()".equals(position)) { cssSelector += ":first-child"; } else if ("last()".equals(position)) { cssSelector += ":last-child"; } else { cssSelector += ":nth-child(" + getPosition() + ")"; } } } } //else { // String baseCssSelector = getItemCssSelector(); // if (StringUtils.isNotEmpty(baseCssSelector)) { // TODO "inject" baseItemPath to elPath // } // } } // add container path if (cssSelector != null && getContainer() != null) { String parentCssSelector = getContainer().getCssSelector(); if (Strings.isNullOrEmpty(parentCssSelector)) { log.warn("Can't generate css selector for parent: {}", getContainer()); cssSelector = null; } else { String root = getRoot(); String deep = ""; if (!Strings.isNullOrEmpty(root)) { if (root.equals("/")) { deep = " > "; } else if (root.equals("//")) { deep = " "; } else { log.warn("this root ({}) is no implemented in css selector: ", root); } } cssSelector = parentCssSelector + deep + cssSelector; } } return cssSelector; } /** * @return final xpath (including containers xpath), used for interacting with browser */ public final String getXPath() { return getXPath(false); } public final String getXPath(boolean disabled) { String returnPath; if (hasElPath()) { returnPath = getElPath(); // String baseItemPath = getBaseItemPath(); // if (!Strings.isNullOrEmpty(baseItemPath)) { // TODO "inject" baseItemPath to elPath // } } else { returnPath = getItemPath(disabled); } returnPath = afterItemPathCreated(returnPath); returnPath = addFinalXPath(returnPath); // add container path if (getContainer() != null) { returnPath = getContainer().getXPath() + returnPath; } return addResultIndexToPath(returnPath); } private String addResultIndexToPath(String xPath) { if (hasResultIdx()) { xPath = "(" + xPath + ")[" + getResultIdx() + "]"; } return xPath; } @Override public String toString() { String info = getInfoMessage(); if (Strings.isNullOrEmpty(info)) { info = itemToString(); } if (WebLocatorConfig.isLogUseClassName() && !getClassName().equals(info)) { info += " - " + getClassName(); } // add container path if (WebLocatorConfig.isLogContainers() && getContainer() != null) { info = getContainer().toString() + " -> " + info; } return info; } public String itemToString() { StringBuilder info = new StringBuilder(); if (hasText()) { info.append(getText()); } else if (hasTitle()) { info.append(getTitle()); } else if (hasId()) { info.append(getId()); } else if (hasName()) { info.append(getName()); } else if (hasBaseCls()) { info.append(getBaseCls()); } else if (hasClasses()) { info.append(classes.size() == 1 ? classes.get(0) : classes.toString()); } else if (hasCls()) { info.append(getCls()); } else if (hasLabel()) { info.append(getLabel()); } else if (hasElPath()) { info.append(getElPath()); } else if (!attribute.isEmpty()) { for (Map.Entry entry : attribute.entrySet()) { info.append("@").append(entry.getKey()).append("=").append(entry.getValue().getValue()); } } else if (hasTag()) { info.append(getTag()); } else { info.append(getClassName()); } return info.toString(); } protected String afterItemPathCreated(String itemPath) { if (hasLabel()) { // remove '//' because labelPath already has and include if (itemPath.indexOf("//") == 0) { itemPath = itemPath.substring(2); } itemPath = getLabelPath() + getLabelPosition() + itemPath; } itemPath = addPositionToPath(itemPath); return itemPath; } protected String addFinalXPath(String itemPath) { if (hasFinalXPath()) { itemPath = itemPath + this.finalXPath; } return itemPath; } protected String addPositionToPath(String itemPath) { if (hasPosition()) { itemPath += applyTemplate("position", getPosition()); } return itemPath; } protected String getLabelPath() { if (searchLabelType.isEmpty()) { searchLabelType.add(SearchType.EQUALS); } SearchType[] st = searchLabelType.toArray(new SearchType[0]); return new WebLocator().setText(getLabel(), st).setTag(getLabelTag()).getXPath(); } @Override @SuppressWarnings("unchecked") public XPathBuilder clone() throws CloneNotSupportedException { XPathBuilder builder = (XPathBuilder) super.clone(); builder.defaultSearchTextType = (List) ((ArrayList) defaultSearchTextType).clone(); builder.searchTextType = (List) ((ArrayList) searchTextType).clone(); builder.searchTitleType = (List) ((ArrayList) searchTitleType).clone(); builder.searchLabelType = (List) ((ArrayList) searchLabelType).clone(); LinkedHashMap templates = (LinkedHashMap) builder.templates; LinkedHashMap templateTitle = (LinkedHashMap) builder.templateTitle; LinkedHashMap templatesValues = (LinkedHashMap) builder.templatesValues; LinkedHashMap elPathSuffix = (LinkedHashMap) builder.elPathSuffix; LinkedHashMap attribute = (LinkedHashMap) builder.attribute; LinkedHashMap attributes = (LinkedHashMap) builder.attributes; builder.templates = (Map) templates.clone(); builder.templatesValues = (Map) templatesValues.clone(); builder.elPathSuffix = (Map) elPathSuffix.clone(); builder.attribute = (Map) attribute.clone(); builder.attributes = (Map) attributes.clone(); builder.templateTitle = (Map) templateTitle.clone(); WebLocator titleTplEl = templateTitle.get("title"); if (titleTplEl != null) { XPathBuilder titleTplElBuilder = titleTplEl.getPathBuilder().clone(); WebLocator titleTplElCloned = new WebLocator().setPathBuilder(titleTplElBuilder); builder.templateTitle.put("title", titleTplElCloned); } return builder; } public List getDefaultSearchTextType() { return this.defaultSearchTextType; } public String getClassName() { return this.className; } public String getRoot() { return this.root; } public String getTag() { return this.tag; } public String getId() { return this.id; } public String getElPath() { return this.elPath; } public String getElCssSelector() { return this.elCssSelector; } public String getBaseCls() { return this.baseCls; } public String getCls() { return this.cls; } public List getClasses() { return this.classes; } public List getExcludeClasses() { return this.excludeClasses; } public String getName() { return this.name; } public String getLocalName() { return this.localName; } public String getText() { return this.text; } public List getSearchTextType() { return this.searchTextType; } public List getSearchTitleType() { return this.searchTitleType; } public List getSearchLabelType() { return this.searchLabelType; } public String getStyle() { return this.style; } public String getTitle() { return this.title; } public Map getTemplates() { return this.templates; } public Map getTemplateTitle() { return this.templateTitle; } public Map getElPathSuffix() { return this.elPathSuffix; } public String getInfoMessage() { return this.infoMessage; } public String getLabel() { return this.label; } public String getLabelTag() { return this.labelTag; } public String getLabelPosition() { return this.labelPosition; } public String getPosition() { return this.position; } public String getResultIdx() { return this.resultIdx; } public String getType() { return this.type; } public Map getAttribute() { return this.attribute; } public Map getAttributes() { return this.attributes; } public boolean isVisibility() { return this.visibility; } public Duration getRender() { return this.render; } @Deprecated public long getRenderMillis() { return this.renderMillis; } public Duration getActivate() { return this.activate; } @Deprecated public int getActivateSeconds() { return this.activateSeconds; } public WebLocator getContainer() { return this.container; } public WebElement getWebElement() { return webElement; } public SearchContext getShadowRoot() { return this.shadowRoot; } public List getChildNodes() { return this.childNodes.getChildNodes(); } public AbstractCell[] getCells() { return this.childNodes.getChildNodes().toArray(new AbstractCell[0]); } public String getFinalXPath() { return this.finalXPath; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy