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)

There is a newer version: 20.08.432.0_b2d2a09
Show newest version
package com.sdl.selenium.web;

import com.sdl.selenium.utils.config.WebDriverConfig;
import com.sdl.selenium.utils.config.WebLocatorConfig;
import com.sdl.selenium.web.utils.Utils;
import com.sdl.selenium.web.utils.internationalization.InternationalizationUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.By;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This class is used to simple construct xpath for WebLocator's
 */
public class XPathBuilder implements Cloneable {
    private static final Logger LOGGER = 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 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 Map attribute = new LinkedHashMap<>();

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

    private boolean visibility;
    private long renderMillis = WebLocatorConfig.getDefaultRenderMillis();
    private int activateSeconds = 60;

    private WebLocator container;
    private List childNodes;

    protected XPathBuilder() {
        setTemplate("visibility", "count(ancestor-or-self::*[contains(@style, 'display: none')]) = 0");
        setTemplate("id", "@id='%s'");
        setTemplate("name", "@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");
    }

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

    /**
     * 

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setRoot(String)} *

root

*
default to "//"
*/ public String getRoot() { return root; } /** *

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 */ public T setRoot(final String root) { this.root = root; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setTag(String)} *

tag (type of DOM element)

*
default to "*"
*/ public String getTag() { return tag; } /** *

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 */ public T setTag(final String tag) { this.tag = tag; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setId(String)} */ public String getId() { return id; } /** *

Used for finding element process (to generate xpath address)

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

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setElPath(String)} *

returned value does not include containers path

*/ public String getElPath() { return elPath; } /** *

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 */ public T setElPath(final String elPath) { this.elPath = elPath; return (T) this; } /** *

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

* * @return value that has been set in {@link #setElCssSelector(String)} *

returned value does not include containers path

*/ public String getElCssSelector() { return elCssSelector; } /** *

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 */ public T setElCssSelector(final String elCssSelector) { this.elCssSelector = elCssSelector; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setBaseCls(String)} */ public String getBaseCls() { return baseCls; } /** *

Used for finding element process (to generate xpath address)

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

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setCls(String)} */ public String getCls() { return cls; } /** *

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 */ public T setCls(final String cls) { this.cls = cls; return (T) this; } /** *

Used for finding element process (to generate xpath address)

*

Example:

*
     *     WebLocator element = new WebLocator().setClasses("bg-btn", "new-btn");
     * 
* * @return value that has been set in {@link #setClasses(String...)} */ public List getClasses() { return classes; } /** *

Used for finding element process (to generate xpath address)

*

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

*
    *
  • Provided classes must be conform css rules.
  • *
* * @param classes list of classes * @param the element which calls this method * @return this element */ public T setClasses(final String... classes) { if (classes != null) { this.classes = Arrays.asList(classes); } return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setExcludeClasses(String...)} */ public List getExcludeClasses() { return excludeClasses; } /** *

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 */ public T setExcludeClasses(final String... excludeClasses) { if (excludeClasses != null) { this.excludeClasses = Arrays.asList(excludeClasses); } return (T) this; } public List getChildNodes() { return childNodes; } public T setChildNodes(final WebLocator... childNodes) { if (childNodes != null) { this.childNodes = Arrays.asList(childNodes); } return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setName(String)} */ public String getName() { return name; } /** *

Used for finding element process (to generate xpath address)

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

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setText(String, SearchType...)} */ public String getText() { return text; } /** *

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 */ 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"); // } if (searchTypes != null && searchTypes.length > 0) { setSearchTextType(searchTypes); } else { this.searchTextType.addAll(defaultSearchTextType); this.searchTextType = cleanUpSearchType(this.searchTextType); } return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setSearchTextType(SearchType...)} */ public List getSearchTextType() { return searchTextType; } /** *

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 */ public T setSearchTextType(SearchType... searchTextTypes) { if (searchTextTypes == null) { this.searchTextType = WebLocatorConfig.getSearchTextType(); } else { this.searchTextType = new ArrayList<>(); Collections.addAll(this.searchTextType, 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 */ public T addSearchTextType(SearchType... searchTextTypes) { if (searchTextTypes != null) { Collections.addAll(this.searchTextType, searchTextTypes); } this.searchTextType = cleanUpSearchType(this.searchTextType); return (T) this; } protected List cleanUpSearchType(List searchTextTypes) { if (searchTextTypes.size() > 1) { Set duplicated = new HashSet<>(); return searchTextTypes.stream() .filter(c -> duplicated.add(c.getGroup())) .collect(Collectors.toList()); } return searchTextTypes; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setSearchLabelType(SearchType...)} */ public List getSearchLabelType() { return searchLabelType; } /** *

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 */ private T setSearchLabelType(SearchType... searchLabelTypes) { this.searchLabelType = new ArrayList<>(); if (searchLabelTypes != null) { Collections.addAll(this.searchLabelType, searchLabelTypes); } this.searchLabelType = cleanUpSearchType(this.searchLabelType); return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setStyle(String)} */ public String getStyle() { return style; } /** *

Used for finding element process (to generate xpath address)

* * @param style of element * @param the element which calls this method * @return this element */ 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.

* * @return value that has been set in {@link #setTitle(String, SearchType...)} */ public String getTitle() { return title; } /** *

Used for finding element process (to generate xpath address)

* * @param title of element * @param searchTypes see {@link SearchType} * @param the element which calls this method * @return this element */ 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; } public T setSearchTitleType(SearchType... searchTitleTypes) { if (searchTitleTypes == null) { this.searchTitleType = WebLocatorConfig.getSearchTextType(); } else { this.searchTitleType = new ArrayList<>(); Collections.addAll(this.searchTitleType, searchTitleTypes); } this.searchTitleType.addAll(defaultSearchTextType); this.searchTitleType = cleanUpSearchType(this.searchTitleType); return (T) this; } public List getSearchTitleType() { return searchTitleType; } public T setTemplateTitle(WebLocator titleEl) { if (titleEl == null) { templateTitle.remove("title"); } else { templateTitle.put("title", titleEl); } return (T) this; } public Map getTemplatesTitle() { return templateTitle; } /** *

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 */ public T setElPathSuffix(final String key, final String elPathSuffix) { if (elPathSuffix == null || "".equals(elPathSuffix)) { this.elPathSuffix.remove(key); } else { this.elPathSuffix.put(key, elPathSuffix); } return (T) this; } public Map getElsPathSuffix() { return elPathSuffix; } 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 */ 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 */ public T setTemplate(final String key, final String value) { if (value == null) { templates.remove(key); } else { templates.put(key, value); } return (T) this; } public T addToTemplate(final String key, final String value) { String template = getTemplate(key); if (StringUtils.isNotEmpty(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

* * @return value that has been set in {@link #setInfoMessage(String)} */ public String getInfoMessage() { return infoMessage; } /** *

Used in logging process

* * @param infoMessage info Message * @param the element which calls this method * @return this element */ public T setInfoMessage(final String infoMessage) { this.infoMessage = infoMessage; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setVisibility(boolean)} */ public boolean isVisibility() { return visibility; } public T setVisibility(final boolean visibility) { this.visibility = visibility; return (T) this; } public long getRenderMillis() { return renderMillis; } public T setRenderMillis(final long renderMillis) { this.renderMillis = renderMillis; return (T) this; } public int getActivateSeconds() { return activateSeconds; } public T setActivateSeconds(final int activateSeconds) { this.activateSeconds = activateSeconds; return (T) this; } // TODO verify what type must return public WebLocator getContainer() { return container; } /** *

Used for finding element process (to generate xpath address)

* * @param container parent containing element. * @param the element which calls this method * @return this element */ public T setContainer(WebLocator container) { this.container = container; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setLabel(String, SearchType...)} */ public String getLabel() { return label; } /** *

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 */ 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)

* * @return value that has been set in {@link #setLabel(String, SearchType...)} */ public String getLabelTag() { return labelTag; } /** *

Used for finding element process (to generate xpath address)

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

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setLabelPosition(String)} */ public String getLabelPosition() { return labelPosition; } /** *

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" */ public T setLabelPosition(final String labelPosition) { this.labelPosition = labelPosition; return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in @{link #setPosition(int)} or @{link #setPosition(Position)} */ public String getPosition() { return position; } /** *

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 */ 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 */ public T setPosition(final Position position) { this.position = position.getValue(); return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setResultIdx(int)} or {@link #setResultIdx(Position)} */ public String getResultIdx() { return resultIdx; } /** *

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 */ 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 */ public T setResultIdx(final Position resultIdx) { this.resultIdx = resultIdx.getValue(); return (T) this; } /** *

Used for finding element process (to generate xpath address)

* * @return value that has been set in {@link #setType(String)} */ public String getType() { return type; } /** *

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 */ 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 */ public T setAttribute(final String attribute, String value, final SearchType... searchTypes) { if (attribute != null) { if (value == null) { this.attribute.remove(attribute); } else { if (!Arrays.asList(searchTypes).contains(SearchType.NOT_INTERNATIONALIZED)) { if (attribute.equals("placeholder") || attribute.equals("alt") || attribute.equals("title")) { value = InternationalizationUtils.getInternationalizedText(value); } } this.attribute.put(attribute, new SearchText(value, searchTypes)); } } return (T) this; } public Map getAttributes() { return attribute; } // ========================================= // =============== Methods ================= // ========================================= /** *

Used only to identify class type of current object

*

Not used for css class!

* * @return string */ public String getClassName() { return className; } protected void setClassName(final String className) { this.className = className; } protected boolean hasId() { return id != null && !"".equals(id); } protected boolean hasCls() { return cls != null && !"".equals(cls); } protected boolean hasClasses() { return classes != null && classes.size() > 0; } protected boolean hasChildNodes() { return childNodes != null && childNodes.size() > 0; } protected boolean hasExcludeClasses() { return excludeClasses != null && excludeClasses.size() > 0; } protected boolean hasBaseCls() { return baseCls != null && !"".equals(baseCls); } protected boolean hasName() { return name != null && !"".equals(name); } protected boolean hasText() { return text != null && !"".equals(text); } protected boolean hasStyle() { return style != null && !"".equals(style); } protected boolean hasElPath() { return elPath != null && !"".equals(elPath); } protected boolean hasTag() { return tag != null && !"*".equals(tag); } protected boolean hasLabel() { return label != null && !"".equals(label); } protected boolean hasTitle() { return title != null && !"".equals(title); } protected boolean hasPosition() { int anInt; try { anInt = Integer.parseInt(position); } catch (NumberFormatException e) { anInt = 1; } return position != null && !"".equals(position) && anInt > 0; } protected boolean hasResultIdx() { int anInt; try { anInt = Integer.parseInt(resultIdx); } catch (NumberFormatException e) { anInt = 1; } return resultIdx != null && !"".equals(resultIdx) && anInt > 0; } protected boolean hasType() { return type != null && !"".equals(type); } // ========================================= // ============ 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 selector = new ArrayList(); CollectionUtils.addIgnoreNull(selector, getBasePath()); CollectionUtils.addIgnoreNull(selector, getItemPathText()); if (!WebDriverConfig.isIE()) { if (hasStyle()) { selector.add("contains(@style ,'" + getStyle() + "')"); } // TODO make specific for WebLocator if (isVisibility()) { // TODO selector.append(" and count(ancestor-or-self::*[contains(replace(@style, '\s*:\s*', ':'), 'display:none')]) = 0"); CollectionUtils.addIgnoreNull(selector, applyTemplate("visibility", isVisibility())); } } return selector.isEmpty() ? "" : StringUtils.join(selector, " and "); } public String getBasePath() { List selector = new ArrayList(); if (hasId()) { selector.add(applyTemplate("id", getId())); } if (hasName()) { selector.add(applyTemplate("name", getName())); } if (hasBaseCls()) { selector.add(applyTemplate("class", getBaseCls())); } if (hasCls()) { selector.add(applyTemplate("cls", getCls())); } if (hasClasses()) { selector.addAll(getClasses().stream().map(cls -> applyTemplate("class", cls)).collect(Collectors.toList())); } if (hasExcludeClasses()) { selector.addAll(getExcludeClasses().stream().map(excludeClass -> applyTemplate("excludeClass", excludeClass)).collect(Collectors.toList())); } if (hasTitle()) { String title = getTitle(); if (!searchTitleType.contains(SearchType.NOT_INTERNATIONALIZED)) { title = InternationalizationUtils.getInternationalizedText(title); } WebLocator titleTplEl = templateTitle.get("title"); if (titleTplEl != null) { titleTplEl.setText(title, searchTitleType.toArray(new SearchType[searchTitleType.size()])); //setTemplate("title", "count(.%s) > 0"); addTemplate(selector, "titleEl", titleTplEl.getXPath()); } else if (!searchTitleType.isEmpty()) { boolean hasContainsAll = searchTitleType.contains(SearchType.CONTAINS_ALL); title = getTextAfterEscapeQuotes(hasContainsAll, title, searchTitleType); selector.add(getTextSearchTypePath(searchTitleType, title, hasContainsAll, "@title")); } else { addTemplate(selector, "title", title); } } if (hasType()) { addTemplate(selector, "type", getType()); } if (!attribute.isEmpty()) { for (Map.Entry entry : attribute.entrySet()) { List searchType = entry.getValue().getSearchTypes(); boolean hasContainsAll = searchType.contains(SearchType.CONTAINS_ALL); String text = getTextAfterEscapeQuotes(hasContainsAll, entry.getValue().getValue(), searchType); selector.add(getTextSearchTypePath(searchType, text, hasContainsAll, "@" + entry.getKey())); } } for (Map.Entry entry : getTemplatesValues().entrySet()) { addTemplate(selector, entry.getKey(), entry.getValue()); } selector.addAll(elPathSuffix.values().stream().collect(Collectors.toList())); selector.addAll(getChildNodesToSelector()); return selector.isEmpty() ? null : StringUtils.join(selector, " and "); } private List getChildNodesToSelector() { List selector = new ArrayList<>(); if (hasChildNodes()) { selector.addAll(getChildNodes().stream().map(this::getChildNodeSelector).collect(Collectors.toList())); } return selector; } 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(null); // break parent tree while generating child address parentElement = parentElementIterator; breakElement = childIterator; } else { childIterator = parentElementIterator; } } String selector = "count(." + child.getXPath() + ") > 0"; if (breakElement != null) { breakElement.setContainer(parentElement); } return selector; } private void addTemplate(List selector, String key, Object... arguments) { String tpl = applyTemplate(key, arguments); if (StringUtils.isNotEmpty(tpl)) { selector.add(tpl); } } protected String applyTemplate(String key, Object... arguments) { String tpl = templates.get(key); if (StringUtils.isNotEmpty(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 += StringUtils.isNotEmpty(selector) ? " and " + subPath : subPath; } selector = getRoot() + getTag() + (StringUtils.isNotEmpty(selector) ? "[" + selector + "]" : ""); return selector; } /** * Construct selector if WebLocator has text * * @return String */ protected String getItemPathText() { String selector = null; if (hasText()) { selector = ""; String text = getText(); if (!searchTextType.contains(SearchType.NOT_INTERNATIONALIZED)) { text = InternationalizationUtils.getInternationalizedText(text); } if (templates.get("text") != null) { return String.format(templates.get("text"), text); } boolean isDeepSearch = searchTextType.contains(SearchType.DEEP_CHILD_NODE) || searchTextType.contains(SearchType.DEEP_CHILD_NODE_OR_SELF); boolean useChildNodesSearch = isDeepSearch || searchTextType.contains(SearchType.CHILD_NODE); String pathText = "text()"; if (useChildNodesSearch) { selector += "count(" + (isDeepSearch ? "*//" : "") + pathText + "["; pathText = "."; } boolean hasContainsAll = searchTextType.contains(SearchType.CONTAINS_ALL) || searchTextType.contains(SearchType.CONTAINS_ALL_CHILD_NODES); text = getTextAfterEscapeQuotes(hasContainsAll, text, searchTextType); selector += getTextSearchTypePath(searchTextType, text, hasContainsAll, pathText); if (useChildNodesSearch) { selector += "]) > 0"; } if (searchTextType.contains(SearchType.DEEP_CHILD_NODE_OR_SELF)) { String selfPath = getTextSearchTypePath(searchTextType, text, hasContainsAll, "."); selector = "(" + selfPath + " or " + selector + ")"; } if (searchTextType.contains(SearchType.HTML_NODE)) { String a = "normalize-space(concat(./*[1]//text(), ' ', text()[1], ' ', ./*[2]//text(), ' ', text()[2], ' ', ./*[3]//text(), ' ', text()[3], ' ', ./*[4]//text(), ' ', text()[4], ' ', ./*[5]//text(), ' ', text()[5]))=" + text; String b = "normalize-space(concat(text()[1], ' ', ./*[1]//text(), ' ', text()[2], ' ', ./*[2]//text(), ' ', text()[3], ' ', ./*[3]//text(), ' ', text()[4], ' ', ./*[4]//text(), ' ', text()[5], ' ', ./*[5]//text()))=" + text; selector = "(" + a + " or " + b + ")"; } } return selector; } private String getTextSearchTypePath(List searchType, String text, boolean hasContainsAll, String pathText) { String selector; if (searchType.contains(SearchType.TRIM)) { pathText = "normalize-space(" + pathText + ")"; } if (searchType.contains(SearchType.CASE_INSENSITIVE)) { pathText = "translate(" + pathText + "," + text.toUpperCase().replaceAll("CONCAT\\(", "concat(") + "," + text.toLowerCase() + ")"; text = text.toLowerCase(); } if (hasContainsAll || searchType.contains(SearchType.CONTAINS_ANY)) { String splitChar = String.valueOf(text.charAt(0)); Pattern pattern = Pattern.compile(Pattern.quote(splitChar)); String[] strings = pattern.split(text.substring(1)); for (int i = 0; i < strings.length; i++) { String escapeQuotesText = Utils.getEscapeQuotesText(strings[i]); if (searchType.contains(SearchType.CONTAINS_ALL_CHILD_NODES)) { if (searchType.contains(SearchType.CASE_INSENSITIVE)) { strings[i] = "count(*//text()[contains(translate(.," + escapeQuotesText.toUpperCase().replaceAll("CONCAT\\(", "concat(") + "," + escapeQuotesText.toLowerCase() + ")," + escapeQuotesText.toLowerCase() + ")]) > 0"; // strings[i] = "(count(*//text()[contains(translate(.," + escapeQuotesText.toUpperCase().replaceAll("CONCAT\\(", "concat(") + "," + escapeQuotesText.toLowerCase() + ")," + escapeQuotesText.toLowerCase() + ")]) > 0 or count(*//text()[contains(translate(.," + escapeQuotesText.toUpperCase().replaceAll("CONCAT\\(", "concat(") + "," + escapeQuotesText.toLowerCase() + ")," + escapeQuotesText.toLowerCase() + ")]) > 0)"; } else { strings[i] = "count(*//text()[contains(.," + escapeQuotesText + ")]) > 0"; // strings[i] = "(count(*//text()[contains(.," + escapeQuotesText + ")]) > 0 or count(..//text()[contains(.," + escapeQuotesText + ")]) > 0)"; } } else { strings[i] = "contains(" + pathText + "," + escapeQuotesText + ")"; } } String operator = hasContainsAll ? " and " : " or "; selector = hasContainsAll ? StringUtils.join(strings, operator) : "(" + StringUtils.join(strings, operator) + ")"; } else if (searchType.contains(SearchType.EQUALS)) { selector = pathText + "=" + text; } else if (searchType.contains(SearchType.STARTS_WITH)) { selector = "starts-with(" + pathText + "," + text + ")"; } else { selector = "contains(" + pathText + "," + text + ")"; } return selector; } private String getTextAfterEscapeQuotes(boolean hasContainsAll, String text, List searchType) { if (hasContainsAll || searchType.contains(SearchType.CONTAINS_ANY)) { return text; } return Utils.getEscapeQuotesText(text); } private String getBaseItemPath() { return getBasePathSelector(); } private String getItemCssSelector() { List selector = new ArrayList(); if (hasTag()) { selector.add(getTag()); } if (hasId()) { selector.add("#" + getId()); } if (hasBaseCls()) { selector.add("." + getBaseCls()); } if (hasCls()) { selector.add("[class=" + getCls() + "]"); } if (hasClasses()) { for (String cls : getClasses()) { selector.add("." + cls); } } if (hasExcludeClasses()) { // LOGGER.warn("excludeClasses is not supported yet"); for (String excludeClass : getExcludeClasses()) { selector.add(":not(." + excludeClass + ")"); } } if (hasName()) { selector.add("[name='" + getName() + "']"); } if (hasType()) { selector.add("[type='" + getType() + "']"); } if (!attribute.isEmpty()) { selector.addAll(attribute.entrySet().stream() .map(e -> "[" + e.getKey() + "='" + e.getValue().getValue() + "']") .collect(Collectors.toList())); } // for (Map.Entry entry : getTemplatesValues().entrySet()) { // addTemplate(selector, entry.getKey(), entry.getValue()); // } // for (String suffix : elPathSuffix.values()) { // selector.add(suffix); // } return selector.isEmpty() ? "*" : StringUtils.join(selector, ""); } public final By getSelector() { String cssSelector = getCssSelector(); return StringUtils.isNotEmpty(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 (StringUtils.isEmpty(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 (StringUtils.isEmpty(parentCssSelector)) { LOGGER.warn("Can't generate css selector for parent: {}", getContainer()); cssSelector = null; } else { String root = getRoot(); String deep = ""; if (StringUtils.isNotEmpty(root)) { if (root.equals("/")) { deep = " > "; } else if (root.equals("//")) { deep = " "; } else { LOGGER.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 (baseItemPath != null && !baseItemPath.equals("")) { // TODO "inject" baseItemPath to elPath } } else { returnPath = getItemPath(disabled); } returnPath = afterItemPathCreated(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 (info == null || "".equals(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() { String info = ""; if (hasText()) { info = getText(); } else if (hasId()) { info = getId(); } else if (hasName()) { info = getName(); } else if (hasClasses()) { info = classes.size() == 1 ? classes.get(0) : classes.toString(); } else if (hasCls()) { info = getCls(); } else if (hasLabel()) { info = getLabel(); } else if (hasTitle()) { info = getTitle(); } else if (hasBaseCls()) { info = getBaseCls(); } else if (hasElPath()) { info = getElPath(); } else if (!attribute.isEmpty()) { for (Map.Entry entry : attribute.entrySet()) { info += "@" + entry.getKey() + "=" + entry.getValue().getValue(); } } else if (hasTag()) { info = getTag(); } else { info = getClassName(); } return info; } 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 addPositionToPath(String itemPath) { if (hasPosition()) { itemPath += "[position() = " + getPosition() + "]"; } return itemPath; } protected String getLabelPath() { if (searchLabelType.size() == 0) { searchLabelType.add(SearchType.EQUALS); } SearchType[] st = searchLabelType.toArray(new SearchType[searchLabelType.size()]); return new WebLocator().setText(getLabel(), st).setTag(getLabelTag()).getXPath(); } @Override public Object clone() throws CloneNotSupportedException { XPathBuilder builder = (XPathBuilder) super.clone(); LinkedHashMap templates = (LinkedHashMap) builder.templates; LinkedHashMap templateTitle = (LinkedHashMap) builder.templateTitle; LinkedHashMap templatesValues = (LinkedHashMap) builder.templatesValues; LinkedHashMap elPathSuffix = (LinkedHashMap) builder.elPathSuffix; builder.templates = (Map) templates.clone(); builder.templatesValues = (Map) templatesValues.clone(); builder.elPathSuffix = (Map) elPathSuffix.clone(); builder.templateTitle = (Map) templateTitle.clone(); WebLocator titleTplEl = templateTitle.get("title"); if (titleTplEl != null) { XPathBuilder titleTplElBuilder = (XPathBuilder) titleTplEl.getPathBuilder().clone(); WebLocator titleTplElCloned = new WebLocator().setPathBuilder(titleTplElBuilder); builder.templateTitle.put("title", titleTplElCloned); } return builder; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy