se.claremont.taf.websupport.webdrivergluecode.WebPageCodeConstructor Maven / Gradle / Ivy
The newest version!
package se.claremont.taf.websupport.webdrivergluecode;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.claremont.taf.core.support.StringManagement;
import se.claremont.taf.core.support.SupportMethods;
import se.claremont.taf.core.testcase.TestCase;
import se.claremont.taf.websupport.DomElement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Class for creating the DomElement descriptions for a draft page class for a WebDriver automation.
* It traverses the current page the WebDriver is at and try to identify the elements on it and create
* access methods for these. This class should be used to speed up page object creation and maintenance.
* It should be possible to improve this class very much, but so far it produces code that is good
* enough to paste into an existing page class. Hence it saves time already although it takes some
* minutes to create the descriptors for large pages.
*
* Created by jordam on 2016-09-28.
*/
class WebPageCodeConstructor {
private final static Logger logger = LoggerFactory.getLogger( WebPageCodeConstructor.class );
private WebDriver driver;
private final Constructors constructors = new Constructors();
private final List methodNames = new ArrayList<>();
private static WebInteractionMethods web;
private static int numberOfUnMappedElements = 0;
private static JavascriptExecutor javascriptDriver = null;
private WebPageCodeConstructor(WebDriver driver){
if(driver == null) return;
this.driver = driver;
}
/**
* Static method to create output file with DomElement descriptions for the web page.
*
* @param driver The web driver instance.
* @param pathToOutputFile Path to the file to be written
* @return Returns a string with a draft page class
*/
@SuppressWarnings({"UnusedReturnValue", "unused"})
static String ConstructWebPageCode(WebDriver driver, String pathToOutputFile, boolean mapEvenBadlyIdentifiedElements){
javascriptDriver = (JavascriptExecutor)driver;
web = new WebInteractionMethods(new TestCase(), driver);
WebPageCodeConstructor webPageCodeConstructor = new WebPageCodeConstructor(driver);
String descriptors = "//Auto-generated with mapCurrentPage() method of WebInteractionMethods." + System.lineSeparator() +
System.lineSeparator() + webPageCodeConstructor.constructWebPageCodeElementByElement(mapEvenBadlyIdentifiedElements);
if(numberOfUnMappedElements > 0){
descriptors += System.lineSeparator() + "//If you run the ConstructWebPageCode() method with mapEvenBadlyIdentifiedElements = true the " + numberOfUnMappedElements + " elements now currently not mapped will get Xpath identifications.";
}
SupportMethods.saveToFile(descriptors, pathToOutputFile);
return descriptors;
}
/**
* Static method to create output file with DomElement descriptions for the web page.
*
* @param driver The web driver instance.
* @param pathToOutputFile Path to the file to be written
* @return Returns a string with a draft page class
*/
@SuppressWarnings("UnusedReturnValue")
static String ConstructWebPageCode(WebDriver driver, String pathToOutputFile){
javascriptDriver = (JavascriptExecutor)driver;
web = new WebInteractionMethods(new TestCase(), driver);
WebPageCodeConstructor webPageCodeConstructor = new WebPageCodeConstructor(driver);
String descriptors = "//Auto-generated with mapCurrentPage() method of WebInteractionMethods." + System.lineSeparator() +
//System.lineSeparator() + webPageCodeConstructor.constructWebPageCodeElementByElement(false);
System.lineSeparator() + webPageCodeConstructor.constructWebPageCodeElementByElement(false);
if(numberOfUnMappedElements > 0){
descriptors += System.lineSeparator() + "//If you run the ConstructWebPageCode() method with mapEvenBadlyIdentifiedElements = true the " + numberOfUnMappedElements + " elements now currently not mapped will get Xpath identifications.";
}
SupportMethods.saveToFile(pageClassHeader(driver.getTitle()) + descriptors + pageClassFooter(), pathToOutputFile);
return descriptors;
}
@SuppressWarnings("UnusedReturnValue")
public static String ConstructWebPageCodeThorough(WebDriver driver, String outputFilePath) {
javascriptDriver = (JavascriptExecutor)driver;
web = new WebInteractionMethods(new TestCase(), driver);
WebPageCodeConstructor webPageCodeConstructor = new WebPageCodeConstructor(driver);
String descriptors = "//Auto-generated with mapCurrentPage() method of WebInteractionMethods." + System.lineSeparator() +
//System.lineSeparator() + webPageCodeConstructor.constructWebPageCodeElementByElement(false);
System.lineSeparator() + webPageCodeConstructor.constructWebPageCodeByTreeTraversing();
if(numberOfUnMappedElements > 0){
descriptors += System.lineSeparator() + "//If you run the ConstructWebPageCode() method with mapEvenBadlyIdentifiedElements = true the " + numberOfUnMappedElements + " elements now currently not mapped will get Xpath identifications.";
}
SupportMethods.saveToFile(descriptors, outputFilePath);
return descriptors;
}
private String unusedMethodName(String suggestedMethodName){
int elementCounter = 2;
String methodNameToTry = suggestedMethodName;
while (methodNameAlreadyUsed(methodNameToTry)){
methodNameToTry = suggestedMethodName + String.valueOf(elementCounter);
elementCounter++;
}
methodNames.add(methodNameToTry);
return methodNameToTry;
}
private static String pageClassHeader(String pageTitle){
StringBuilder sb = new StringBuilder();
sb.append("import se.claremont.autotest.websupport.DomElement;").append(System.lineSeparator());
sb.append(System.lineSeparator());
String className = StringManagement.methodNameWithOnlySafeCharacters(pageTitle);
if(className.length() > 50) className = className.substring(0,50);
sb.append("public class ").append(className.substring(0, 1).toUpperCase()).append(className.substring(1)).append("Page {").append(System.lineSeparator());
sb.append(System.lineSeparator());
return sb.toString();
}
private static String pageClassFooter(){
return System.lineSeparator() + "}" + System.lineSeparator();
}
private boolean methodNameAlreadyUsed(String nameToTry){
return methodNames.stream().anyMatch(m -> m.equals(nameToTry));
}
/**
* This is the actual method that produce the DomElements for the page, and returns them as a string.
*
* @return Returns a string with the relevant objects.
*/
private String constructWebPageCodeElementByElement(boolean mapEvenBadlyIdentifiedElements){
//In the best of worlds this method should recursively traverse down the DOM tree and try identifying leaf
// nodes in the tree. If all leaf nodes for a parent are identified the parent does not have to be identified.
List webElements = driver.findElements(By.xpath("//body//*"));
for(WebElement webElement : webElements){
Constructor constructor = attemptAddElementConstructor(webElement, mapEvenBadlyIdentifiedElements);
if(constructor == null)continue;
constructors.addConstructor(constructor);
}
return constructors.toString();
}
@SuppressWarnings("WeakerAccess")
public String constructWebPageCodeByTreeTraversing(){
addConstructorForSubElementsOf("//body");
return constructors.toString();
}
private void addConstructorForSubElementsOf(String rootNodeXpath){
for(WebElement child : getChildren(rootNodeXpath)){
if(!hasBranschingChildren(generateXPATH(child, ""))){ //no child element has more than one child recursive over children until leaf
Constructor constructor = attemptIdentifyElementConstructorRecursive(child);
if(constructor != null) {
String nameSuggestion = identifyBestName(child);
if(nameSuggestion != null && nameSuggestion.length() > 0){
constructor.setName(nameSuggestion);
}
constructors.addConstructor(constructor);
}
} else { //At least one of the children, or childrens children recursive has branches
addConstructorForSubElementsOf(generateXPATH(child, ""));
}
}
}
private String identifyBestName(WebElement element){
String text = getAnyTextFromAnyChildren(element);
if(text != null && text.length() > 0 && text.length() < 50)
return StringManagement.methodNameWithOnlySafeCharacters(unusedMethodName(text.replace("\"", "\\\"") + "_" + tagNameToElementSuffix(element.getTagName())));
text = getAnyAttributeFromAnyChildren(element);
if(text != null && text.length() > 0)
return StringManagement.methodNameWithOnlySafeCharacters(unusedMethodName(text.replace("\"", "\\\"") + "_" + tagNameToElementSuffix(element.getTagName())));
return null;
}
private String getAnyAttributeFromAnyChildren(WebElement webElement){
if(webElement == null)return null;
Map attributes = getAttributes(webElement);
if(attributes != null && attributes.size() > 0){
String attributeString = "";
for(String attributeKey : attributes.keySet()){
attributeString += attributeKey + "_" + attributes.get(attributeKey) + "_";
}
attributeString += tagNameToElementSuffix(webElement.getTagName());
return StringManagement.methodNameWithOnlySafeCharacters(unusedMethodName(attributeString));
}
List children = getChildren(webElement);
for(WebElement child : children){
String attibutes = getAnyAttributeFromAnyChildren(child);
if(attibutes != null){
return attibutes;
}
}
return null;
}
private WebElement getParent(WebElement child){
WebElement parent = null;
try{
parent = child.findElement(By.xpath(".."));
}catch (Exception e){
System.out.println(e.toString());
}
return parent;
}
private String getAnyTextFromAnyChildren(WebElement webElement){
if(webElement == null) return null;
List children = getChildren(webElement);
if(children.size() == 0){ //Leaf
String text = webElement.getText();
if(text != null && text.length() > 0){
return text;
}
WebElement parent = webElement;
while (parent != null){
parent = getParent(webElement);
text = parent.getText();
if(text != null && text.length() > 0){
return text;
}
webElement = parent;
}
} else {
return getAnyTextFromAnyChildren(children.get(0));
}
return null;
}
private Constructor attemptIdentifyElementConstructorRecursive(WebElement webElement){
Constructor constructor = attemptAddElementConstructor(webElement, false);
if(constructor != null) return constructor;
List children = getChildren(generateXPATH(webElement, ""));
for(WebElement child : children){
constructor = attemptIdentifyElementConstructorRecursive(child);
if(constructor != null) return constructor;
}
return null;
}
@SuppressWarnings("SimplifiableIfStatement")
private boolean hasBranschingChildren(String xpathOfParent){
List children = getChildren(xpathOfParent);
if(children.size() < 1) return false;
if(children.size() > 1) return true;
return hasBranschingChildren(xpathOfParent + "/*[1]");
}
private List getChildren(WebElement rootElement){
String elementXpath = generateXPATH(rootElement, "");
return driver.findElements(By.xpath(elementXpath + "/*"));
}
private List getChildren(String rootElementXpath){
return driver.findElements(By.xpath(rootElementXpath + "/*"));
}
@SuppressWarnings("unchecked")
private Map getAttributes(WebElement element){
Map attributes = null;
Map returnMap = new HashMap<>();
if(element == null)return returnMap;
try{
attributes = (Map)javascriptDriver.executeScript("var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;", element) ;
}catch (Exception e){
System.out.println("Could not get attributes for element. " + e.toString());
}
if(attributes == null) return returnMap;
for(String key : attributes.keySet()){
returnMap.put(key, (String)attributes.get(key));
}
return returnMap;
}
private Constructor attemptAddElementConstructor(WebElement webElement, boolean mapEvenBadlyIdentifiedElements){
try{
String recognitionString = null;
String id = webElement.getAttribute("id");
String tagName = webElement.getTagName();
if(id != null && id.length() > 0){
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(id) + "_" + tagNameToElementSuffix(tagName);
recognitionString = id;
if((suggestedElementName + recognitionString).length() < 200 && recognitionOnlyHasOneMatch(recognitionString, DomElement.IdentificationType.BY_ID)){
recognitionString = recognitionString.replace("\"", "\\\"");
String suggestedElementConstructorString = "\"" + recognitionString + "\", DomElement.IdentificationType.BY_ID";
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
}
}
String name = webElement.getAttribute("name");
if(name != null && name.length() > 0){
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(name) + "_" + tagNameToElementSuffix(tagName);
recognitionString = name;
if((suggestedElementName + recognitionString).length() < 200 && recognitionOnlyHasOneMatch(recognitionString, DomElement.IdentificationType.BY_NAME)){
recognitionString = recognitionString.replace("\"", "\\\"");
String suggestedElementConstructorString = "\"" + recognitionString + "\", DomElement.IdentificationType.BY_NAME";
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
}
}
String text = null;
if(tagName.equals("a")){
text = webElement.getText();
if(text != null && text.length() > 0) {
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(text) + "_" + "Link";
recognitionString = text;
if (
!recognitionString.contains(System.lineSeparator()) &&
!recognitionString.contains("\n") &&
!recognitionString.contains("\r") &&
(suggestedElementName + recognitionString).length() < 200 &&
recognitionOnlyHasOneMatch(recognitionString, DomElement.IdentificationType.BY_LINK_TEXT)) {
recognitionString = recognitionString.replace("\"", "\\\"");
String suggestedElementConstructorString = "\"" + recognitionString + "\", DomElement.IdentificationType.BY_LINK_TEXT";
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
}
}
}
//https://suitcss.github.io/
String elementClass = webElement.getAttribute("class");
if( elementClass != null && elementClass.length() > 0) {
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(elementClass) + "_" + tagNameToElementSuffix(tagName);
recognitionString = elementClass;
if((suggestedElementName + recognitionString).length() < 200 && recognitionOnlyHasOneMatch(recognitionString, DomElement.IdentificationType.BY_CLASS)){
recognitionString = recognitionString.replace("\"", "\\\"");
String suggestedElementConstructorString = "\"" + recognitionString + "\", DomElement.IdentificationType.BY_CLASS";
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
}
}
Map attributes = getAttributes(webElement);
if(attributes != null){ //Maybe a combinatorical search for more than two attributes could be a futute feature?
boolean matchFound = false;
for(String key : attributes.keySet()){
if(key.toLowerCase().equals("class") || key.toLowerCase().equals("id")) continue;
recognitionString = key + "=" + attributes.get(key);
if(recognitionString.length() < 150 && recognitionOnlyHasOneMatch(recognitionString, DomElement.IdentificationType.BY_ATTRIBUTE_VALUE)){
matchFound = true;
break;
}
}
if(matchFound){
recognitionString = recognitionString.replace("\"", "\\\"");
String suggestedElementConstructorString = "\"" + recognitionString + "\", DomElement.IdentificationType.BY_ATTRIBUTE_VALUE";
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(recognitionString.replace("=", "_")) + "_" + tagNameToElementSuffix(tagName);
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
} else if(attributes.size() > 1){
for(int i = 0; i < attributes.size()-1; i++){
for(int j = i+1; i < attributes.size(); i++){
String key1 = (String)attributes.keySet().toArray()[i];
String key2 = (String)attributes.keySet().toArray()[j];
recognitionString = "//" + tagName + "[@" + key1 + "='" + attributes.get(key1) + "' and @" + key2 + "='" + attributes.get(key2) + "']" ;
recognitionString = recognitionString.replace("\"", "\\\"");
if(recognitionString.length() < 200 && recognitionOnlyHasOneMatch(recognitionString, DomElement.IdentificationType.BY_X_PATH)){
//matchFound = true;
String suggestedElementConstructorString = "\"" + recognitionString + "\", DomElement.IdentificationType.BY_ATTRIBUTE_VALUE";
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(recognitionString.replace("=", "_")) + "_" + tagNameToElementSuffix(tagName);
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
}
}
}
}
}
if(text == null){
text = webElement.getText();
}
if(text != null &&
text.length() > 0 &&
!text.contains(System.lineSeparator()) &&
!text.contains("\n") &&
!text.contains("\r") &&
text.length() < 200)
{
List matchingElements = null;
try {
matchingElements = driver.findElements(By.xpath("//" + tagName + "[contains(text(),'" + webElement.getText() + "')]"));
} catch (Exception e) {
System.out.println("Problems matching elements for page contruction: " + e.toString());
}
if (matchingElements != null && matchingElements.size() == 1) {
String suggestedElementName = StringManagement.methodNameWithOnlySafeCharacters(webElement.getText()) + "_" + tagNameToElementSuffix(tagName);
String suggestedElementConstructorString = "";
int numberOfElementsFound = driver.findElements(By.xpath("//" + tagName + "[contains(text(),'" + text + "')]")).size();
if (numberOfElementsFound == 1) {
suggestedElementConstructorString += "\"//" + tagName + "[contains(text(),'" + text.replace("\"", "\\\"").replace("'", "\"") + "')]\", DomElement.IdentificationType.BY_X_PATH";
return new Constructor(unusedMethodName(suggestedElementName), suggestedElementConstructorString);
}
}
}
//Default
if(mapEvenBadlyIdentifiedElements){
return new Constructor(unusedMethodName("Badly_identified_element"), "\"" + generateXPATH(webElement, "") + "\", DomElement.IdentificationType.BY_XPATH");
} else {
numberOfUnMappedElements++;
}
}catch (Exception e){ //Probably stale element
System.out.println("Could not create costructor for element. " + e.toString());
}
return null;
}
private static String generateXPATH(WebElement childElement, String current) {
String childTag = childElement.getTagName();
if(childTag.equals("html")) {
return "/html[1]"+current;
}
WebElement parentElement = childElement.findElement(By.xpath(".."));
List childrenElements = parentElement.findElements(By.xpath("*"));
int count = 0;
//noinspection ForLoopReplaceableByForEach
for(int i=0;i{
int elementCounter = 1;
void addConstructor(Constructor constructor){
if(hasUniqueName(constructor)){
this.add(constructor);
} else {
constructor.elementName = constructor.elementName + Integer.toString(elementCounter);
elementCounter++;
this.add(constructor);
}
}
boolean hasUniqueName(Constructor constructor){
for(Constructor constr : this){
if(constr.elementName.equals(constructor.elementName)) return false;
}
return true;
}
@SuppressWarnings("unused")
public boolean hasUniqueDescriptor(Constructor constructor){
for(Constructor constr : this){
if(constr.constructorString.equals(constructor.constructorString)) return false;
}
return true;
}
public @Override String toString(){
StringBuilder stringBuilder = new StringBuilder();
for(Constructor constructor : this){
stringBuilder.append(constructor.toString());
}
return stringBuilder.toString();
}
}
private class Constructor{
String elementName;
final String constructorString;
Constructor(String elementName, String constructorString){
this.elementName = elementName;
this.constructorString = constructorString;
logger.debug( "Creating: " + SupportMethods.LF + this.toString() );
System.out.println("Creating element: " + System.lineSeparator() + toString());
}
void setName(String name){
elementName = name;
}
public @Override String toString(){
return SupportMethods.LF + "public static DomElement " + elementName + "() {" + SupportMethods.LF + " return new DomElement (" + constructorString + ");" + SupportMethods.LF + "}" + SupportMethods.LF;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy