How to handle refresh when using Appium POM (java-client)

Hi, I am using java-client 7.6 and JDK 11 with Page Object Model (POM).

The android app that I test refreshes during the test and it causes me some problems.
when I implemented the page object/appium widget classes I used selenium’s FluentWait inside the PO/widget methods to wait for elements to become visible/clickable before performing any action on them (getText/click).

  • The problem: if the screen is refreshing while waiting for element to become visible/clickable appium will throw a NoSuchElementException wrapping a StaleElementReferenceException. I expect the FluentWait instance to throw the exception (because it is not ignored) and to catch the exception in the @Test method inside the test class.
    but for some reason, the FluentWait instance does not throw the exception so it is never getting to the @Test method and the element’s proxy keeps using stale element for lookup.

added a sample code, let me know if you have any suggestions or ideas for different implementations. or what might cause the Fluent Wait instance to not throwing the unignored exception?

//widget class
public class WidgetExample extends Widget {

    protected WidgetExample(WebElement element) {
        super(element);
    }

    @AndroidFindBy(id = "text box locator")
    private WebElement textBoxElement;

    public WebElement getTextBoxElement() {
        return textBoxElement;
    }

    public String getTextBoxText() {
        String result = "";

        //option 1
        result = textBoxElement.getText();

        //option 2
        result = getText(textBoxElement);

        return result;
    }

    private String getText(WebElement element) {
        return new FluentWait<WebDriver>(driver)
                .withTimeout(Duration.ofSeconds(30))
                .until(visibilityOf(element))
                .getText();
    }

    //page object class
    public class PageObjectExample {

        @AndroidFindBy(id = "widget locator")
        private WidgetExample widgetExample;

        public WidgetExample getWidgetExample() {
            return widgetExample;
        }
    }


    //test class
    public class TestClassExample {

        //wait instance
        WebDriverWait webDriverWait = new WebDriverWait(driver, 30);
        //page object
        PageObjectExample pageObjectExample;

        //-------- init page object instance with PageFactory and AppiumFieldDecorator --------

        @Test
        public void textBoxTest() {
            //the screen might get refreshed during the lookup!
            //desired behaviour - invoke the condition *again* from the beginning if it (returns false/null) or (throws one of the exceptions: NoSuchElementException/StaleElementReferenceException)
            waitFor(refreshed(webDriver -> pageObjectExample.getWidgetExample().getTextBoxText().equals("expected text")));
            //keep testing
            //.....
        }

        private <T> T waitFor(ExpectedCondition<T> expectedCondition) {
            return webDriverWait.until(expectedCondition);
        }
    }
  • I am currently implementing the getTextBoxText with option 2
    maybe it does not have such a difference but if I want to click an element I must wait for it to become clickable first.

why so hard? why not just simple →

PageFactory.initElements(new AppiumFieldDecorator(driver, Duration.ofSeconds(SEC_NEEDED)), this);

plus there is also annotation:

@WithTimeout(time = 5, unit = TimeUnit.SECONDS) // look for 5 seconds for this element
@AndroidFindBy(id = "widget locator")
private WidgetExample widgetExample;

check also example → PageObject Initialization Best Practices - #8 by Aleksei

yes. I know. @Aleksei
I saw other implementation like element.getText() or element.click() but doing as so is equivalent to this.findElement("some locator").click() - it does not mean the element is clickable/visible it just means the element is present.

For android it is 100% visible (and in 99.99% it is clickable). For ios it can be outside screen (depends how you write locator).

Ps and appium annotation not equal driver.findElement…

Pps also disable all animation on test phones and it will help to fix minor issues.

Hi @Aleksei, thanks for the response.

  1. I can tell that when I did not wait for some elements to become clickable it did cause me some issues… in anyway, it is better be safe than sorry. And a FluentWait instance without any ignored exceptions should do the work! and it does it great in all the other parts of the test except of this particular case. for some reason, when there is a stale element the FluentWait does not throw the exception. do you have any assumptions or ideas why it might happen? (using FluentWait and not AppiumFluentWait).
    also, I set allowInvisibleElements android capability to true because I test both android and iOS in the same repository and I want theirs logic to be as similar as it can get.

  2. if it does not equal to driver.findElement… so what is it equal to then?
    it is a proxy that executes searchContext.findElement(givenLocator) inside a WebDriverWait instance (with the specified lookup timeout) each time a method is invoked on the instance. meaning, it returns the element when it is preset (not necessarily visible/clickable)

  3. How can I achieve it in android and iOS? I saw for android the capability disableWindowAnimation and for iOS any idea? (I use fullRest cap as well so the devices are getting wiped with each test)

  1. Many depends on your app: animation, if it freezes a bit right after open new screen and so on. Android automation fast enough to tap on element while it appearing with animation BUT your app not accept taps in such mode. Also it depends on your code. Try wrote something like:
        myApp()
                .forgotPassCode()
                .forgottenPasscodePage()
                .isForgottenPasscodePageLoaded() // always check that correct screen loaded before action
                .tapVerifyIdentityButton()
                .isSelectResetModePageLoaded() // always check that correct screen loaded before action
                .tapSomeButton();
  1. Appium annotation can search whatever difficult way you need. Example -> try write it with driver.find… :slight_smile:
    @AndroidFindBy(id = "miv_name")
    @AndroidFindAll(value = {
            @AndroidBy(id = "auto_complete"),
            @AndroidBy(className = "android.widget.EditText")
    }, priority = 1)
    @iOSXCUITFindBys(value = {
            @iOSXCUITBy(id = "companyName"),
            @iOSXCUITBy(className = "XCUIElementTypeTextField")
    })
    private WebElement companyNameInput;
  1. https://github.com/appium/appium-xcuitest-driver -> see appium:reduceMotion
    https://github.com/appium/appium-uiautomator2-driver#capabilities -> see appium:disableWindowAnimation

ok. so, let’s say I use element.getText() and element.click() without any wait condition in both android and iOS.
how would you recommend to handle a refresh screen? inside a WebDriverWait? I cannot know when the screen will get refreshed…
I do as follow for now:
waitFor(refreshed(webDriver -> pageObjectExample.getWidgetExample().getTextBoxText().equals("expected text")));
or for click:
waitFor(refreshed(webDriver -> pageObjectExample.getWidgetExample().clickSomeButton())); //because it might get stale during the action and I want the whole “lookup chain” to have fresh search context so I need the condition to run again from the beginning.

do you have any suggestion or more correct methods to implement the same?

important:

  • A lot of the times I get StaleElementReferenceException (let’s assume I use a 30 sec Page Object instance) - and appium keeps searching with the stale element for 30 seconds and does not throw the exception. how so?

when looking on appium java code I can see the follow:

catch (TimeoutException | StaleElementReferenceException e) {
        throw new NoSuchElementException(format(exceptionMessageIfElementNotFound, bySearching.toString()), e);
    }

so it supposed to throw a NoSuchElementException wrapping a StaleElementReferenceException and should stop the proxy lookup. instead even when getting a stale element exception it goes on and look for the element inside the proxy for the whole 30 seconds…

I personally use just Appium annotations (which I set default 20 sec element search). Then in code always wait first needed screen to appear. Finally do action.
Now all actions are routed not ONE function. One tap, one getText, one setValue and to on.
example with Allure and logs code lines:

    @Step("Tap 'Continue' dialog button")
    public CB_ReviewPage tapContinueDialogButton() {
        Logger.log();
        Assert.assertTrue(tap(continueDialogButton), createAssertionLog("FAILED"));
        return new CB_ReviewPage(page);
    }

    @Step("Get 'Amount input' text")
    public String getAmountInputText() {
        Logger.log();
        setLookTiming(3); // 3 sec MAX just to minimize time when it fails to find element and this speedup tests when many fails
        String result = getText(amountInput);
        setDefaultTiming();
        return result;
    }

    @Step("Set 'Amount' input {amount}")
    public CB_SetupPage setAmountInput(String amount) {
        Logger.log("amount: '" + amount + "'");
        Assert.assertTrue(clearInput(amountInput), createAssertionLog("FAILED"));
        Assert.assertTrue(setValue(amountInput, amount), createAssertionLog("FAILED"));
        return this;
    }

And here really finally in tap I have double logic to tap again after few mSec (e.g. 200) if i catch StaleElementReferenceException or Could not proxy command to remote server.

Anyway -> If you prefer to use waitFor use it in ONE place for ALL instead of with each element in code.

I don’t think I understood you correctly, I do separate the test logic from the waitFor and getText methods. the code in the post was just for demonstration.

I will try to make the explanation of my problem more understandable.

  • Let’s assume a test case like so: there is a textBox element on the screen with the default text of “0”, in order to enter a new value, I need to click on it, a dialog opens and I enter the number “5”. then, click the “Done” button on the dialog. the dialog should get closed and the textBox’s value should be “5”.

  • Possible code implementation example:
    Test Class:

pageObject.getTextViewWidget().clickTextBox();
pageObject.getTextBoxDialog().setText(5);
pageObject.getTextBoxDialog().clickDone();
assertEquals(pageObject.getTextViewWidget().getTextBoxText(), “5”, “text box’s value isn’t 5 as expected”);

  • The problem: when the code gets to the assertEquals(…) line we might get an error. why? because after clicking on the dialog’s “Done” button, the textBox’s value maybe still “0”. why again? because it may take for the screen (depends on internet connection and server timings) a couple of seconds or milliseconds to change the textBox’s value. if our assertEquals was executed in that time gap then we will get one of two things:
  1. an assertion error: the value did not change yet, pageObject.getTextViewWidget().getTextBoxText() will return “0”
  2. an exception - if, the element was redrawn(refresh) during pageObject.getTextViewWidget().getTextBoxText() it will cause the element to become stale and it will throw a StaleElementReferenceException.
  • Possible solution: wait for the field to be “5”

pageObject.getTextViewWidget().clickTextBox();
pageObject.getTextBoxDialog().setText(5);
pageObject.getTextBoxDialog().clickDone();
//waiting for the field to populate
new WebDriverWait(driver, Duration.ofSeconds(30))
.ignore(StaleElementReferenceException.class) //ignoring NoSuchElementException by default.
.until(webDriver → pageObject.getTextViewWidget().getTextBoxText().equals(“5”));
//testing with assertion - does not necessary because the textBox’s value is already “5”
assertEquals(pageObject.getTextViewWidget().getTextBoxText(), “5”, “text box’s value isn’t 5 as expected”);

Now, if any StaleElementReferenceException will occur, the WebDriverWait will catch the exception and will execute the provided ExpectedCondition lambda again - this, will cause the elements’ proxies(because we are using POM) to locate the “fresh” element after the textBox field was populated with “5”.

  • Another possible solution: to add a sleep or wait between clicking the dialog’s Done button and executing the assertEquals(…) command. The problem with that is that we count on external factors and we cannot know for sure that the field is populated after our Thread.sleep(…); Additionally, we want the Wait/sleep to finish as soon as possible to avoid unnecessary execution time.

  • A problem with our first solution: let’s assume our pageObject instance waits up to 30 secs when trying to locate an element. Based on AppiumElementLocator
    if a StaleElementReferenceException occurs, it won’t throw it until the 30 secs (page object’s lookup duration) is done. So if there is a stale element after 2 seconds, appium will try to use the stale element(to locate a sub element) for additional 28 seconds before throwing an exception. This will cause the WebDriverWait to avoid executing the ExpectedCondition again until 30 secs are over.

  • My two solutions for the problem:

  1. to use a zero lookup page object instance. Let’s assume we have a page object with Duration.ZERO. then, if we will run the following:

pageObject.getTextViewWidget().clickTextBox();
pageObject.getTextBoxDialog().setText(5);
pageObject.getTextBoxDialog().clickDone();
//waiting for the field to populate
new WebDriverWait(driver, Duration.ofSeconds(30))
.ignore(StaleElementReferenceException.class) //ignoring NoSuchElementException by default.
.until(webDriver → zeroPageObject.getTextViewWidget().getTextBoxText().equals(“5”));
//testing with assertion - does not necessary because the textBox’s value is already “5”
assertEquals(pageObject.getTextViewWidget().getTextBoxText(), “5”, “text box’s value isn’t 5 as expected”);

appium will throw a NoSuchElementException wrapping a TimeoutException and then our ExpectedCondition lambda function will be invoked again whether or not we got a StaleElementReferenceException. - this a bit annoying since we need to manage our page object instances correctly and have maybe different instances for one page, each with different lookup timeout.
2. To override appium’s AppiumElementLocator class and allow it to throw StaleElementReferenceException without waiting for the whole 30 secs of the page object. Then, we will be able to use one instance for each page object and run the ExpectedCondition lambda with page object instances with any lookup timeout we want. We Just need to remember that if we change data of expecting for the elements on the screen to be redrawn we must use the wait instance.

Those are my ways of thinking, let me know yours. How would you handle a refresh page, there are pages that refreshes more than once and we cannot know when the refresh will be done.

if there is a stale element, should appium locate all the chain backwards?

let’s assume I have widget3 inside widget2 inside widget1

if there is a StaleElementReferenceException while running .getText() on the last element widget1.getWidget2().getWidget3().getText()
should appium locate automatically widget1 -> widget2 -> widget3 again or it will keep locating only widget3?

what is inside function? aaaa it is PageObject itself?

no. I added it afterwards. because the driver cannot be stale pageObject.getTextBoxText() won’t throw StaleElementReferenceException.

getTextViewWidget() - return a widget instance containing the textBox element.

  • assume the textBox is inside a container(the widget)
    getTextViewWidget == getTextBoxContainer
    so to get to it I invoke: getTextBoxContainer().getTextBoxText()

I will give another example:

@Test    
public void test() {
        Widget3 widget3 = pageObject.getWidget1().getWidget2().getWidget3();
        try {
            widget3.click(); //when clicking widget2 is stale, hence widget3 cannot be found
        } catch (StaleElementReferenceException ignored) {
            widget3.click(); //won't work - will throw a StaleElementReferenceException as well because widget3's context(widget2 WebElement) is still stale
            pageObject.getWidget1().getWidget2().getWidget3().click(); //will work - because invoking all the "lookup chain" from the beginning, will cause appium to look for all the WebElements again.
        }
    }

it is equal to something like → ?

driver
  .findElement(AppiumBy.id("widget1_ID"))
  .findElement(AppiumBy.id("widget2_ID"))
  .findElement(AppiumBy.id("widget3_ID"))
  .click();

here are 3 times can be staleElement and each time you should start from beginning!

Try instead annotation like:

    @AndroidFindBys(value = {
            @AndroidBy(id = "widget1_ID"),
            @AndroidBy(id = "widget2_ID"),
            @AndroidBy(id = "widget3_ID")
    })
    private WebElement widget3_Container;

yes. they are equal. The only difference is that the first one will throw an exception after the pageObject’s timeout is over (.e.g 30 secs). And the second one will throw it immediately. The single condition they are exactly the same is when we use a page object which has a 0 lookup timeout (also not exactly the same because the exceptions will be different but they will throw it on the same time).

Thanks for the tip. In my opinion, the last example loses all the appium Widget concept. My goal is to have three different classes (widgets 1, 2, 3) and to chain the calls in the Test class to create nesting lookup. I don’t want to use widget1’s and widget2’s locators inside widget3.

I think I’ll just invoke the whole “lookup chain” again.

I just wondered what is the right way to handle refresh with POM.
Note that if I want to invoke all the calls again, I must use a page object which has 0 lookup duration. otherwise, appium won’t throw the exception unless the 30 secs (for example) are over…

The other way is to customize AppiumElementLocator which is not public and the java-client’s PageFactory API in particular AppiumFieldDecorator does not accept customize ElementLocator even if I would create my own…
Although I managed to customize it and use it, I am sure this is not the way the “author” meant.

second one (Appium annotation AndroidFindBy…) will be trying to MAX time you set with initElements or finaly whole chain will be found.

Hi @Aleksei.
First of all, I must say thank you! I decided on a way I want to implement that, which is a bit similar to yours.
Your help is not taken for granted; So, thanks again!

Btw, when trying to disable/reduce animation in both android and iOS with the capabilities you suggested, I saw that when using disableWindowAnimation there was a major change in the UX while using the app compare to iOS that did not have any change at all with reduceMotion. Should it be like that? maybe I do something wrong? (although I doubted).
*using an emulator && a simulator.

I am on real device. And i disable animation in debug menu on phone with all devices.

1 Like