com.sdl.selenium.web.XPathBuilder Maven / Gradle / Ivy
Show all versions of Testy Show documentation
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 extends WebLocator> getChildNodes() {
return this.childNodes.getChildNodes();
}
public AbstractCell[] getCells() {
return this.childNodes.getChildNodes().toArray(new AbstractCell[0]);
}
public String getFinalXPath() {
return this.finalXPath;
}
}