appium / java-client

Java language binding for writing Appium Tests, conforms to W3C WebDriver Protocol

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Page Object design extension question

SergeyErmakovMercDev opened this issue · comments

Hello,
I'm using new page object design with Widget class. And recently caught some unexpected behaviour.
Let me describe my classes:

public class Movies {
    @iOSFindBy(uiAutomator = ".tableViews()[0].cells()")
    public List<Movie> items;
}

public class Movie extends Widget {
    @iOSFindBy(uiAutomator = ".staticTexts()[0]")
    public IOSElement nameElement;

    public IOSElement getNameElement() {
       return nameElement;
    }
}

And these classes I use smth like this:

Movies movies = ...
List<Movie> items = movies.items; //items is not null
Movie item = items.get(0);

IOSElement fieldElement = item.nameElement; // fieldElement is null
IOSElement methodElement = item.getNameElement(); // methodElement is not null

Why does it work so?
Are there any ways to get element directly from field without creating getter?

Hi @SergeyErmakovMercDev

It is not a bug. It works by design.

  • The first point.
    Are you familiar with Page Object design pattern? Please pay attention to requirenments. So the thing you want conflicts with the pattern spec most likely. I think it makes sense to implement a public method which describes some business-logic/logic of interaction with a widget instead.
  • The second point.
    Have you read this description? Please pay attention to the** "What if interaction with a "widget" has special details for each used platform, but the same at high-level"**. Thing that you want may become senseless when you will want to design something crossplatform.

If it is interesting I can describe how does it work.

IOSElement fieldElement = items.nameElement; // fieldElement is null

because items is not the real object. It is a proxy created via CGLIB. A real object with populated fields is instantiated once when any declared method is invoked. But it stays not available anyway.

I'm waiting for your response.

@TikhomirovSergey, thanks for responce.

I've just read the requirements and in most cases in project our team comply them. But in this situation I need to interact with sub-elements in widject directly. Like this:

public class Movies {
    @iOSFindBy(uiAutomator = ".tableViews()[0].cells()")
    public List<Movie> items;

    public void swapMovies(int indexA, int indexB) {
         Point centerA = items.get(indexA).getNameElement().getCenter();
         Point centerB = items.get(indexB).getNameElement().getCenter();

         getDriver().swipe(centerA.getX(), centerA.getY(), centerB.getX(), centerB.getY(), DEFAULT_DURATION);
         // and so on
    }
}

Of cource I understand that I can make simple getNameCenter() method at Movie and the problem will be closed. But we are using Cucumber + Appium in project, thus in step definitions we get elements via reflection. The reason why we do this is simple: decrease coding time.

So, everything is okey if we get fields via reflection from pages. And we supposed that widgets work familiar. But they don't.

Sergey, I'll be so grateful to you, if you'll explain how does this work. And why in one case I got null at other an object.

BTW. Don't afraid about cross-platform. We simply don't need it :)

@SergeyErmakovMercDev
Ok. I got your problem. And it seem I have the solution...

If it is interesting I can explain how does it work.

A decorator instance scans a page object instance. If a field has a type of some WebElement/list of WebElement then the decorator works as usual:
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java#L155

Ok. It tries to create a widget otherwise:
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java#L160
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java#L200
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java#L205

It creates not real objects, it creates proxies:
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/utils/ProxyFactory.java

Real objects which wrapped inside proxies are created when any declared method is invoked:
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java#L51
https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java

So the problem is that there is no ability to exract a real widget-object, What if there was something like that:

   @FindBy
   Widget widget;

   ...
   Object someValue = widget.
           getSelfReference(). //this method would be used in some extraordinary situations like yours
               publicField;

?

It could fix the known problem:
#267
Point: What if interaction with a "widget" has special details for each used platform, but the same at high-level
To find: ...for now it is not possible to

You can propose a PR if you want :) Also don't forget to add a test if you want to try.

This issue has been fixed and it is going to be published soon