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

com.seleniumtests.uipage.ByC Maven / Gradle / Ivy

/**
 * Orignal work: Copyright 2015 www.seleniumtests.com
 * Modified work: Copyright 2016 www.infotel.com
 * Copyright 2017-2019 B.Hecquet
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.seleniumtests.uipage; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.commons.collections.ListUtils; import org.apache.commons.lang.NotImplementedException; import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebElement; import org.openqa.selenium.internal.FindsByXPath; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebElement; import com.seleniumtests.core.SeleniumTestsContextManager; import com.seleniumtests.customexception.ScenarioException; import com.seleniumtests.driver.BrowserType; import com.seleniumtests.driver.CustomEventFiringWebDriver; import com.seleniumtests.driver.DriverConfig; import com.seleniumtests.driver.WebUIDriver; public class ByC extends By { public String getEffectiveXPath() { throw new NotImplementedException("XPath not implemented"); } private static final String ERROR_CANNOT_FIND_ELEMENT_WITH_SUCH_CRITERIA = "Cannot find element with such criteria "; @Override public List findElements(SearchContext context) { return new ArrayList<>(); } /** * Search first 'input' element after label referenced by name * @param label * @return */ public static ByC labelForward(final String label) { return labelForward(label, null, false, null); } /** * Search first element for tagName after label referenced by name * @param label * @param tagName * @return */ public static ByC labelForward(final String label, final String tagName) { return labelForward(label, tagName, false, null); } /** * Search first element for {@code tagName} after label referenced by partial name * Use case is {@code

some label

} * @param label label to search * @param tagName tag name after this label. The element we really search * @param labelTagName if label is not in a {@code
{@code
     * 
     * 		#shadow-root
     * 		
     * 			#shadow-root
     * 			
* * * } *
* * You would write *
{@code
     * HtmlElement shadowRoot = new HtmlElement("", ByC.shadow(By.tagName("host1"), By.tagName("host2")));
     * HtmlElement myElement = new HtmlElement("", By.id("el"), shadowRoot);
     * }
* * @param bies * @return */ public static ByC shadow(By... bies) { return new Shadow(bies); } /** * method for searching an element by a locator or an other. * It also checks if the locator is relevant to the tested platform (in case of mobile), which allow to write * * ByC.or(android(By.tagName("input")), ios(By.xpath(""))) * @author S047432 * */ public static ByC or(By... bies) { return new Or(bies); } /** * Says that the locator in parameter is for android only. It doesn't do anything else and should be used with ByC.or() to have an effect * @param by * @return */ public static ByC android(By by) { return new Android(by); } /** * Says that the locator in parameter is for iOS only. It doesn't do anything else and should be used with ByC.or() to have an effect * @param by * @return */ public static ByC ios(By by) { return new Ios(by); } /** * Search an element by id using xpath * @param id * The value of the "id" attribute to search for. * @return A By which locates elements by the value of the "id" attribute. */ public static By xId(String id) { return new ByAttribute("id", id); } /** * Search an element by link text using xpath * @param linkText * The exact text to match against. * @return A By which locates A elements by the exact text it displays. */ public static By xLinkText(String linkText) { return new ByText(linkText, "*", false); } /** * Search an element by partial link text using xpath * @param partialLinkText * The partial text to match against * @return a By which locates elements that contain the given link text. */ public static By xPartialLinkText(String partialLinkText) { return new ByText(partialLinkText, "*", true); } /** * Search an element by name using xpath * @param name * The value of the "name" attribute to search for. * @return A By which locates elements by the value of the "name" attribute. */ public static By xName(String name) { return new ByAttribute("name", name); } /** * Search an element by tag name using xpath * @param tagName * The element's tag name. * @return A By which locates elements by their tag name. */ public static By xTagName(String tagName) { return new ByXTagName(tagName); } /** * Find elements based on the value of the "class" attribute. If an element has * multiple classes, then this will match against each of them. For example, if * the value is "one two onone", then the class names "one" and "two" will * match. * * @param className * The value of the "class" attribute to search for. * @return A By which locates elements by the value of the "class" attribute. */ public static By xClassName(String className) { return new ByXClassName(className); } protected static String buildSelectorForText(String text) { String escapedText; if (text.endsWith("*") || text.endsWith("^") || text.endsWith("$")) { escapedText = escapeQuotes(text.substring(0, text.length() - 1)); } else { escapedText = escapeQuotes(text); } if (text.endsWith("*")) { return String.format("[contains(text(),%s)]", escapedText); } else if (text.endsWith("^")) { return String.format("[starts-with(text(),%s)]", escapedText); } else if (text.endsWith("$")) { // return String.format("[ends-with(text(),%s)]", escapedText); //not valid for xpath 1.0 return String.format("[substring(text(), string-length(text()) - string-length(%s) +1) = %s]", escapedText, escapedText); } else { return String.format("[text() = %s]", escapedText); } } public static class ByLabelForward extends ByC implements Serializable { private static final long serialVersionUID = 5341968046120372161L; private String label; private String tagName; private String labelTagName; // tag of the label we are searching. default is label private boolean partial; /** * * @param label Content of the label to search * @param tagName Tag name of the element following label, we want to get. Default is "input" * @param partial do we search for partial of full label name * @param labelTagName tag name of the label element. Default is "label" */ public ByLabelForward(String label, String tagName, boolean partial, String labelTagName) { if (label == null) { throw new IllegalArgumentException("Cannot find elements with a null label attribute."); } this.label = label; this.tagName = tagName == null ? "input" : tagName; this.partial = partial; this.labelTagName = labelTagName == null ? "label" : labelTagName; } @Override public String getEffectiveXPath() { if (partial && !label.endsWith("*")) { label += "*"; } return String.format(".//%s%s/following::%s", labelTagName, buildSelectorForText(label), tagName); } @Override public List findElements(SearchContext context) { return ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); } @Override public WebElement findElement(SearchContext context) { return ((FindsByXPath) context).findElementByXPath(getEffectiveXPath()); } @Override public String toString() { return String.format("By.label %s:'%s' forward on element %s", labelTagName, label, tagName); } } public static class ByLabelBackward extends ByC implements Serializable { private static final long serialVersionUID = 5341968046120372162L; private String label; private final String tagName; private final boolean partial; private final String labelTagName; // tag of the label we are searching. default is label /** * * @param label Content of the label to search * @param tagName Tag name of the element following label, we want to get. Default is "input" * @param partial do we search for partial of full label name * @param labelTagName tag name of the label element. Default is "label" */ public ByLabelBackward(String label, String tagName, boolean partial, String labelTagName) { if (label == null) { throw new IllegalArgumentException("Cannot find elements with a null label attribute."); } this.label = label; this.tagName = tagName == null ? "input" : tagName; this.partial = partial; this.labelTagName = labelTagName == null ? "label" : labelTagName; } @Override public String getEffectiveXPath() { if (partial && !label.endsWith("*")) { label += "*"; } return String.format(".//%s%s/preceding::%s", labelTagName, buildSelectorForText(label), tagName); } @Override public List findElements(SearchContext context) { return ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); } @Override public WebElement findElement(SearchContext context) { List elements; elements = ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); List elementsReverse = elements.subList(0, elements.size()); Collections.reverse(elementsReverse); return elementsReverse.get(0); } @Override public String toString() { return String.format("By.label %s:'%s' backward on element %s", labelTagName, label, tagName); } } public static class ByAttribute extends ByC implements Serializable { private static final long serialVersionUID = 5341968046120372161L; private String attributeName; private String attributeValue; public ByAttribute(String attributeName, String attributeValue) { if (attributeName == null) { throw new IllegalArgumentException("Cannot find elements with a null attribute."); } if (attributeValue == null) { throw new IllegalArgumentException("Cannot find elements with a null attribute value."); } this.attributeName = attributeName; this.attributeValue = attributeValue; } @Override public String getEffectiveXPath() { return String.format(".//*%s", buildSelector()); } /** *Build a xpath selector so that we understand the CSS syntax: https://www.w3schools.com/cssref/css_selectors.asp * '*' => contains * '^' => starts with * '$' => ends with * @return */ private String buildSelector() { String escapedAttributeValue = escapeQuotes(attributeValue); if (attributeName.endsWith("*")) { String tmpAttributeName = attributeName.substring(0, attributeName.length() - 1); return String.format("[contains(@%s,%s)]", tmpAttributeName, escapedAttributeValue); } else if (attributeName.endsWith("^")) { String tmpAttributeName = attributeName.substring(0, attributeName.length() - 1); return String.format("[starts-with(@%s,%s)]", tmpAttributeName, escapedAttributeValue); } else if (attributeName.endsWith("$")) { String tmpAttributeName = attributeName.substring(0, attributeName.length() - 1); //return String.format("[ends-with(@%s,%s)]", attributeName, escapedAttributeValue); // would by valid with xpath 2.0 return String.format("[substring(@%s, string-length(@%s) - string-length(%s) +1) = %s]", tmpAttributeName, tmpAttributeName, escapedAttributeValue, escapedAttributeValue); } else { return String.format("[@%s=%s]", attributeName, escapedAttributeValue); } } @Override public List findElements(SearchContext context) { return ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); } @Override public WebElement findElement(SearchContext context) { return ((FindsByXPath) context).findElementByXPath(getEffectiveXPath()); } @Override public String toString() { return String.format("By.attribute: %s='%s'", attributeName, attributeValue); } } /** * Find element with the text content given * @author s047432 * */ public static class ByText extends ByC implements Serializable { private static final long serialVersionUID = 5341968046120372161L; private String text; private String tagName; private boolean partial; public ByText(String text, String tagName, boolean partial) { if (text == null) { throw new IllegalArgumentException("Cannot find elements with a null text content."); } if (tagName == null) { throw new IllegalArgumentException("Cannot find elements with a null tagName."); } this.text = text; this.tagName = tagName; this.partial = partial; } @Override public String getEffectiveXPath() { return String.format(".//%s%s", tagName, buildSelectorForText(text)); } @Override public List findElements(SearchContext context) { if (partial && !text.endsWith("*")) { text += "*"; } return ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); } @Override public WebElement findElement(SearchContext context) { if (partial && !text.endsWith("*")) { text += "*"; } return ((FindsByXPath) context).findElementByXPath(getEffectiveXPath()); } @Override public String toString() { return String.format("%s By.text: '%s'", tagName, text); } } /** * Allow to search elements with several criteria * It will create intersection between a search for each criteria * @author s047432 * */ public static class And extends ByC implements Serializable { /** * */ private static final long serialVersionUID = 6341968046120372161L; private transient By[] bies; public And(By... bies) { if (bies.length == 0) { throw new ScenarioException("At least on locator must be provided for And"); } for (By by : bies) { if (by == null) { throw new IllegalArgumentException("Cannot find elements with a null element"); } } this.bies = bies; } @Override public List findElements(SearchContext context) { List elements = bies[0].findElements(context); for (int i = 1; i < bies.length; i++) { elements = ListUtils.retainAll(elements, bies[i].findElements(context)); } return elements; } @Override public WebElement findElement(SearchContext context) { try { return findElements(context).get(0); } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(ERROR_CANNOT_FIND_ELEMENT_WITH_SUCH_CRITERIA + toString()); } } @Override public String toString() { List biesString = new ArrayList<>(); for (By by : bies) { biesString.add(by.toString()); } return String.join(" and ", biesString); } } public static class Shadow extends ByC implements Serializable { private static final long serialVersionUID = 6341668046120372161L; private transient By[] bies; public Shadow(By... bies) { if (bies.length == 0) { throw new ScenarioException("At least on locator must be provided for shadow "); } this.bies = bies; } /** * If multiple "By" are pro */ @Override public List findElements(SearchContext context) { List elements = new ArrayList<>(); for (By by : bies) { List hosts; if (elements.isEmpty()) { // first iteration hosts = by.findElements(context); } else { hosts = elements.get(0).findElements(by); elements = new ArrayList<>(); // reset list because we don't care parent elements } JavascriptExecutor js = (JavascriptExecutor) WebUIDriver.getWebDriver(false); DriverConfig driverConfig = WebUIDriver.getWebUIDriver(false).getConfig(); if ((driverConfig.getBrowserType() == BrowserType.CHROME || driverConfig.getBrowserType() == BrowserType.EDGE) && driverConfig.getMajorBrowserVersion() >= 96) { for (WebElement host : hosts) { Map roots = ((Map) js.executeScript("return arguments[0].shadowRoot", host)); RemoteWebElement shadowRootElement = new RemoteWebElement(); shadowRootElement.setParent((RemoteWebDriver) ((CustomEventFiringWebDriver) WebUIDriver.getWebUIDriver(false).getDriver()).getWebDriver()); shadowRootElement.setId((String) roots.values().toArray()[0]); elements.add(shadowRootElement); } } else if (driverConfig.getBrowserType() == BrowserType.CHROME && driverConfig.getMajorBrowserVersion() < 96) { for (WebElement host : hosts) { elements.add((WebElement) (js.executeScript("return arguments[0].shadowRoot", host))); } } else { throw new NotImplementedException("Shadow DOM not supported for " + driverConfig.getBrowserType()); } // stop if no element is found if (elements.isEmpty()) { return new ArrayList<>(); } } return elements; } @Override public WebElement findElement(SearchContext context) { try { return findElements(context).get(0); } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(ERROR_CANNOT_FIND_ELEMENT_WITH_SUCH_CRITERIA + toString()); } } @Override public String toString() { List biesString = new ArrayList<>(); for (By by : bies) { biesString.add(by.toString()); } return String.join("/", biesString); } } /** * Class for searching an element by a locator or an other. * It also checks if the locator is relevant to the tested platform (in case of mobile), which allow to write * * ByC.or(android(By.tagName("input")), ios(By.xpath(""))) * @author S047432 * */ public static class Or extends ByC implements Serializable { private transient By[] bies; private static final long serialVersionUID = 6341968046167372161L; public Or(By... bies) { if (bies.length == 0) { throw new ScenarioException("At least on locator must be provided"); } for (By by : bies) { if (by == null) { throw new IllegalArgumentException("Cannot find elements with a null element"); } } this.bies = bies; } @Override public List findElements(SearchContext context) { String platform = SeleniumTestsContextManager.getThreadContext().getPlatform(); List elements = new ArrayList<>(); for (By by : bies) { // check this 'by' applies to the platform if ((by instanceof Android && !platform.equalsIgnoreCase("android")) || (by instanceof Ios && !platform.equalsIgnoreCase("ios")) ) { continue; } else if (by instanceof ByPlatformSpecific) { by = ((ByPlatformSpecific) by).getBy(); } elements = by.findElements(context); // stop once at least an element is found if (!elements.isEmpty()) { break; } } return elements; } @Override public WebElement findElement(SearchContext context) { try { return findElements(context).get(0); } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(ERROR_CANNOT_FIND_ELEMENT_WITH_SUCH_CRITERIA + toString()); } } @Override public String toString() { List biesString = new ArrayList<>(); for (By by : bies) { biesString.add(by.toString()); } return String.join(" or ", biesString); } } /** * * */ public static class Android extends ByC implements Serializable, ByPlatformSpecific { private static final long serialVersionUID = 6341968046120092161L; private transient By by; public Android(By by) { this.by = by; } @Override public List findElements(SearchContext context) { throw new UnsupportedOperationException("You cannot use ByC.android directly"); } @Override public WebElement findElement(SearchContext context) { throw new UnsupportedOperationException("You cannot use ByC.android directly"); } @Override public String toString() { return String.format("android[%s]", by.toString()); } @Override public By getBy() { return by; } } /** * * */ public static class Ios extends ByC implements Serializable, ByPlatformSpecific { private static final long serialVersionUID = 6341468046120372161L; private transient By by; public Ios(By by) { this.by = by; } @Override public List findElements(SearchContext context) { throw new UnsupportedOperationException("You cannot use ByC.ios directly"); } @Override public WebElement findElement(SearchContext context) { throw new UnsupportedOperationException("You cannot use ByC.ios directly"); } @Override public String toString() { return String.format("ios[%s]", by.toString()); } @Override public By getBy() { return by; } } public static class ByXTagName extends By implements Serializable { private static final long serialVersionUID = 4699295846984948351L; private final String tagName; public ByXTagName(String tagName) { if (tagName == null) { throw new IllegalArgumentException("Cannot find elements when the tag name is null."); } this.tagName = tagName; } public String getEffectiveXPath() { return ".//" + tagName; } @Override public List findElements(SearchContext context) { return ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); } @Override public WebElement findElement(SearchContext context) { return ((FindsByXPath) context).findElementByXPath(getEffectiveXPath()); } @Override public String toString() { return "By.tagName: " + tagName; } } public static class ByXClassName extends By implements Serializable { private static final long serialVersionUID = -8737882849130394673L; private final String className; public ByXClassName(String className) { if (className == null) { throw new IllegalArgumentException( "Cannot find elements when the class name expression is null."); } this.className = className; } public String getEffectiveXPath() { return (".//*[" + containingWord("class", className) + "]"); } @Override public List findElements(SearchContext context) { return ((FindsByXPath) context).findElementsByXPath(getEffectiveXPath()); } @Override public WebElement findElement(SearchContext context) { return ((FindsByXPath) context).findElementByXPath(getEffectiveXPath()); } /** * Generate a partial XPath expression that matches an element whose specified attribute * contains the given CSS word. So to match <div class='foo bar'> you would say "//div[" + * containingWord("class", "foo") + "]". * * @param attribute name * @param word name * @return XPath fragment */ private String containingWord(String attribute, String word) { return "contains(concat(' ',normalize-space(@" + attribute + "),' '),' " + word + " ')"; } @Override public String toString() { return "By.className: " + className; } } protected static String escapeQuotes(String aString) { if (!aString.contains("'")) { return "'" + aString + "'"; } else { StringBuilder newString = new StringBuilder("concat("); for (String part : aString.split("'")) { newString.append("'" + part + "',\"'\","); } return newString.substring(0, newString.length() - 5) + ")"; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy