com.sdl.selenium.web.XPathBuilder Maven / Gradle / Ivy
Show all versions of Testy Show documentation
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;
}
}