Can I organize objects in page object model with arrays or hash maps?

笑着哭i 提交于 2019-12-08 11:50:31

问题


I'm new to Selenium automation. And I have fair knowledge in java.

I have created test script to use for user registration.

I have used page object model for this. This is my page object script.

This is What I use

public class SIgnUpTest extends PageObject {

@FindBy(id="merchantName")
private WebElement merchant;

@FindBy(id="merchantCode")
private WebElement code;

@FindBy(id="categoryId")
private WebElement category;

@FindBy(id="description")
private WebElement description;

@FindBy(id="merchantLogo")
private WebElement logo;

@FindBy(id="btnNextStep1")
private WebElement Next;

public SIgnUpTest(WebDriver driver) {
    super(driver);
}
public void enterName(String name, String code,String description){
    this.merchant.sendKeys(name);
    this.code.sendKeys(code);
    this.description.sendKeys(description); 
}
public void Logo(String Logo){
    this.logo.sendKeys(Logo);       
}
public void Next() {
    Next.click();
}

I have about 100 objects to add like this. How can I organize this for long term use?

Is there a way to use than repeat the @Find by and WebElement?

I have looked for arrays and hash maps. But I have no idea how to use it.

Can I use a two dimensional array or hash map for this? If so how?

Thank you.

I want something like this to use. Is it possible?

Is there some way I can use something similar to the following that I seek:

Following code has issues.

public class SIgnUpTest extends PageObject {
public void objects (){

SortedMap sm = new TreeMap();
sm.put("merchantName", "merchant");
sm.put("merchantCode", "code");
sm.put("categoryId", "category");
sm.put("description", "description");
sm.put("merchantLogo", "Logo");
sm.put("Next", "Next")

for(int i=0; i<sm.keySet().size(); i++){
@FindBy(id=sm.keySet());
}
for(int i=0; i<sm.keySet().size(); i++){
    private WebElement sm.values(); 
}   
}   
public SIgnUpTest(WebDriver driver) {
    super(driver);
}
public void enterName(String name, String code,String description){
    this.merchant.sendKeys(name);
    this.code.sendKeys(code);
    this.description.sendKeys(description); 
}
public void Logo(String Logo){
    this.logo.sendKeys(Logo);       
}   
public void Next() {
    Next.click();
}

}

回答1:


Yes, there are multiple ways to resolve this. I'm guessing you have a big page with a lot of elements you need to interact with. One of the ways it to not use the pagefactory method (Which is often confused with PageObject model).

You may use the driver instance to find the elements you need as and when required using driver.findElement(By.xpath("//xpath/to/element"));

Or, you may use inner classes to encapsulate the different modules on a page logically. I suggest you go through the following links for more clarity regarding the design decision first:

https://sqa.stackexchange.com/questions/14889/how-do-i-split-my-pageobject-model-classes https://martinfowler.com/bliki/PageObject.html




回答2:


In my experience I favor ObjectMap -over- PageObject.

ObjectMap (helps avoid learning an entire PageObj complex abstraction layer and taxonomy) can be found in ---Selenium Testing Tools Cookbook, 2nd Edition--- book and seleniumeasy. Think of it as Composition-over-inheritance, favor more flexible and powerful one, avoid design complex taxonomies. Also supports Data-driven approach, selectors are NOT hardcoded in your code == no configuration possible. PageObj ties your code with complex abstractions, so in order to reuse it you always need this particular page in your web sites. Configuration files have their place, but when they are packaged with the code and not intended to be updated by the user, it takes just as many steps to update the value in the configuration file as it does to update it in the code, so there's really no encapsulation taking place. Here Dynamic Forms(PageObj/BEM) are constructed run-time based on tests' needs. No complex forms taxonomies again.

example

basic_contact > zip_code_contact > dealer (message+dealer info header) > quote (model+trim) > test_drive (calendar) > contact_us (email+chat)

Here are some considerations before jumping into POMs (like holmium.core and page-objects) right away:

  1. Automatically built Page Objects are hard to maintain and to use. Pretty much no grouping of elements into headers and footers, or identified widgets, there would just be a big list of stuff — and would the automatically generated names read well enough to explain what they were for?

  2. It might limit your design, e.g. you staring to ignore better abstractions.

  3. Not enough flexibility, especially for refactoring (both structure and implementation).

Find more at dzone. Implementation for Java here and here.




回答3:


You will need to create custom annotation, elementfactory and decorator to accomplish this. Add the appropriate imports in the code below.

Annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface MultipleFind {

    String delimiter() default "@@";
    String[] elementDetails();
}

Sample PageObject - usernameInput@@ID@@username - The first part will be used as key in the Map. The second part is the locator strategy, this will correspond to the values in How.java enum. The third part is the locator. This pageobject can contain standard annotations like FindBy etc along with the custom annotation.

public class LoginPageObject extends BasePageObject<LoginPageObject> {

    //Conventional declaration
    @FindBy(how=How.ID, using="username")
    private WebElement usernameInput;

    //Conventional declaration
    @FindBy(how=How.NAME, using="pwd")
    private WebElement passwordInput;

    //Custom multiple annotation
    @MultipleFind(elementDetails = { "usernameInput@@ID@@username", "passwordInput@@NAME@@pwd" })
    private Map<String, WebElement> loginui;        

    public LoginMapPageObject(WebDriver driver) {           
        super(driver);

        //PageFactory initialization with custom factory and decorator
        MapElementLocatorFactory melf = new MapElementLocatorFactory(driver);
        MapFieldDecorator mfd = new MapFieldDecorator(melf);
        PageFactory.initElements(mfd, this);
    }

    private void enterAndSubmitLoginDetails() {         
        //Conventional calls
        //usernameInput.sendKeys("user");
        //passwordInput.sendKeys("password");

        //Call to retrieve element from map
        loginui.get("usernameInput").sendKeys("user");
        loginui.get("passwordInput").sendKeys("password");
    }       
}

MapAnnotations -

public class MapAnnotations extends Annotations {
    public MapAnnotations(Field field) {
        super(field);
    }

    public By buildBy() {
        if (getField().getAnnotation(MultipleFind.class) != null)
            return null;
        return super.buildBy();
    }

    public Map<String, By> buildMapBys() {
        Map<String, By> details = null;
        Optional<Annotation> annot = Arrays.asList(getField().getAnnotations())
                .stream()
                .filter(a -> a.annotationType().equals(MultipleFind.class))
                .findAny();
        if(annot.isPresent()) {
            details = createLocatorDetails(annot.get());
        }
        return details;
    }

    private Map<String, By> createLocatorDetails(Annotation annot) {
        String[] elemDets = ((MultipleFind) annot).elementDetails();
        String delim = ((MultipleFind) annot).delimiter();
        Map<String, By> details = Arrays.stream(elemDets)
                .map(d -> d.split(delim))
                .collect(Collectors.toMap(a -> a[0], a -> createBy(a[1], a[2])));
        return details;
    }

    private By createBy(String howStr, String using) {
        How how = How.valueOf(howStr);
        switch (how) {
          case CLASS_NAME:
            return By.className(using);
          case CSS:
            return By.cssSelector(using);
          case ID:
          case UNSET:
            return By.id(using);
          case ID_OR_NAME:
            return new ByIdOrName(using);
          case LINK_TEXT:
            return By.linkText(using);
          case NAME:
            return By.name(using);
          case PARTIAL_LINK_TEXT:
            return By.partialLinkText(using);
          case TAG_NAME:
            return By.tagName(using);
          case XPATH:
            return By.xpath(using);
          default:
            // Note that this shouldn't happen (eg, the above matches all
            // possible values for the How enum)
            throw new IllegalArgumentException("Cannot determine how to locate element ");
        }
    }

    protected void assertValidAnnotations() {
        FindBys findBys = getField().getAnnotation(FindBys.class);
        FindAll findAll = getField().getAnnotation(FindAll.class);
        FindBy findBy = getField().getAnnotation(FindBy.class);
        MultipleFind multFind = getField().getAnnotation(MultipleFind.class);
        if (multFind != null
                && (findBys != null || findAll != null || findBy != null)) {
            throw new IllegalArgumentException(
                    "If you use a '@MultipleFind' annotation, "
                            + "you must not also use a '@FindBy' or '@FindBys' or '@FindAll' annotation");
        }
        super.assertValidAnnotations();
    }
}

MapElementLocatorFactory

public class MapElementLocatorFactory implements ElementLocatorFactory {

    private final SearchContext searchContext;

      public MapElementLocatorFactory(SearchContext searchContext) {
        this.searchContext = searchContext;
      }

      public MapElementLocator createLocator(Field field) {
        return new MapElementLocator(searchContext, field);
      }
}

MapElementLocator

public class MapElementLocator extends DefaultElementLocator {

    private Map<String, By> elementBys;
    private SearchContext searchContext;

    public MapElementLocator(SearchContext searchContext, Field field) {
        this(searchContext, new MapAnnotations(field));
    }

    public MapElementLocator(SearchContext searchContext,MapAnnotations annotations) {
        super(searchContext, annotations);
        this.elementBys = annotations.buildMapBys();
        this.searchContext = searchContext;
    }

    public WebElement findElement(String elementName) {
        By by = elementBys.get(elementName);
        return searchContext.findElement(by);
    }
}

MapFieldDecorator

public class MapFieldDecorator extends DefaultFieldDecorator {

    public MapFieldDecorator(ElementLocatorFactory factory) {
        super(factory);
    }

    public Object decorate(ClassLoader loader, Field field) {
        if (Map.class.isAssignableFrom(field.getType())) {          
            MapElementLocator locator = (MapElementLocator) factory.createLocator(field);           
            return proxyForMapLocator(loader, locator);
        }
        return super.decorate(loader, field);
    }

    @SuppressWarnings("unchecked")
    protected Map<String, WebElement> proxyForMapLocator(ClassLoader loader,
            MapElementLocator locator) {
        InvocationHandler handler = new LocatingMapElementHandler(locator);
        Map<String, WebElement> proxy;
        proxy = (Map<String, WebElement>) Proxy.newProxyInstance(loader,
                new Class[] { Map.class }, handler);
        return proxy;
    }
}

LocatingMapElementHandler

public class LocatingMapElementHandler implements InvocationHandler {
    private final MapElementLocator locator;

    public LocatingMapElementHandler(MapElementLocator locator) {
        this.locator = locator;
    }

    public Object invoke(Object object, Method method, Object[] objects) throws Throwable {
        if(method.getName() != "get")
            throw new UnsupportedOperationException("Only get method of Map is supported for this proxy.");     
        return locator.findElement((String)objects[0]);
    }
}

This will only work with individual WebElement not List. You can try to implement it, though the problem is erasure removes generic information so impossible to determine correctly if you want single or multiple elements. Only get() method will work on the Map proxy, others will throw UnSupportedException.



来源:https://stackoverflow.com/questions/47195901/can-i-organize-objects-in-page-object-model-with-arrays-or-hash-maps

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!