1

In my team we're doing cross platform UI testing using Appium and the Appium Java-Client. The current structure of our project is something like:

mobile
   pages
     SignInPage
   steps
     SignInSteps

The steps are "glued" together using Cucuember. SignInPage looks something like this:

public class SignInPage {

    public SignInPage(AppiumDriver driver) {
        PageFactory.initElements(new AppiumFieldDecorator(driver, 15, TimeUnit.SECONDS), this);
    }

    // region Identifiers
    final String IOS_USERNAME_FIELD = "SignInUsernameField";
    final String ANDROID_USERNAME_FIELD = "new UiSelector().resourceIdMatches(\".*id/username.*\")";
    final String IOS_PASSWORD_FIELD = "SignInPasswordField";
    final String ANDROID_PASSWORD_FIELD = "new UiSelector().resourceIdMatches(\".*id/password_editText.*\")";
    final String IOS_SIGN_IN_BUTTON = "SignInButton";
    final String ANDROID_SIGN_IN_BUTTON = "new UiSelector().resourceIdMatches(\".*id/signInButton.*\")";
    // endregion

    @iOSFindBy(accessibility = IOS_USERNAME_FIELD)
    @AndroidFindBy(uiAutomator = ANDROID_USERNAME_FIELD)
    private MobileElement usernameField;

    @iOSFindBy(accessibility = IOS_PASSWORD_FIELD)
    @AndroidFindBy(uiAutomator = ANDROID_PASSWORD_FIELD)
    private MobileElement passwordField;

    @iOSFindBy(accessibility = IOS_SIGN_IN_BUTTON)
    @AndroidFindBy(uiAutomator = ANDROID_SIGN_IN_BUTTON)
    private MobileElement signInButton;

    public MobileElement getUsernameField() {
        return usernameField;
    }

    public MobileElement getPasswordField() {
        return passwordField;
    }

    public MobileElement getSignInButton() {
        return signInButton;
    }

    public void tapUsernameField() {
        getUsernameField().click();
    }

    public void tapSignInButton() {
        getSignInButton().click();
    }

    public void clearUsernameEditText() {
        getUsernameField().clear();
    }
}

We're not sure in terms of performance and elements lookup where is it best to create an instance of the SignInPage. Currently we have a @Before method in our SignInSteps that is executed before each Gherkin scenario starts (which is not ideal) but it helps us having a SignInPage property in the SignInSteps class that is reused by all the steps.

public class SignInSteps {

    private SignInPage signInPage;
    AppiumDriver driver;

    @Before()
    public void setUp() throws MalformedURLException {
        driver = TestBase.getInstance().getDriver();
        signInPage = new SignInPage(driver);
    }

    @Given("I fill in the username and password")
    public void fill_username_and_password() throws Throwable {
        signInPage.tapUsernameField();
        signInPage.clearUsernameEditText();
        fillEditText(signInPage.getUsernameField(), PropertiesManager.getInstance().getValueForKey(Constants.SIGN_IN_USERNAME));
        fillEditText(signInPage.getPasswordField(), PropertiesManager.getInstance().getValueForKey(Constants.SIGN_IN_PASSWORD));
    }
  // Other sign in steps below
}

However I feel that a cleaner approach would be to create the SignInPage as a local variable inside each step method in SignInSteps. Is there any performance impact in creating the page(s) you need in each step?

Also, it's not clear to me, with our current approach (the @Before approach) why exactly does it work even when you create a page for some steps that will be executed later on (so the screen is not even visible at this point).

So maybe the larger question would be how are the elements looked up? Is it when calling PageFactory.initElements(new AppiumFieldDecorator(driver, 15, TimeUnit.SECONDS), this); or when actually accessing the annotated properties (which would be some kind of lazy initialization approach that from my knowledge Java doesn't have, unless my understanding of Java annotations is wrong).

Sorry for the long post, but these are some things that I want to understand thoroughly. So any help is highly appreciated.

Thank you!

Cosmin
  • 2,840
  • 5
  • 32
  • 50

1 Answers1

1

I did some more research (debugging) and I've found the answer:

When you call PageFactory.initElements(new AppiumFieldDecorator(driver, 15, TimeUnit.SECONDS), this); the annotated properties from the page are set (decorated) via reflection (see AppiumFieldDecorator) with a proxy (ElementInterceptor) that wraps a MobileElement. Each time you call a method on the annotated property you actually call the proxy that looks up the element and forwards the method call. There is no cache in between (as opposed to WidgetInterceptor which I didn't figured out yet where it is used).

So in my case, creating the page once, or in each step doesn't really make a difference because the element lookup is performed each time you interact with it (which I guess it's good, but it might have a performance impact also).

I've also attached a few screenshots below:

Stacktrace when you call PageFactory.initElements(new AppiumFieldDecorator(driver, 15, TimeUnit.SECONDS), this);

enter image description here

enter image description here

Stacktrace when you call click on an element

enter image description here

enter image description here

Hope this helps others as well understand how the tool works.

Cosmin
  • 2,840
  • 5
  • 32
  • 50