Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wait for button Visibility To matches #6

Open
m0agh opened this issue Oct 14, 2017 · 6 comments
Open

Wait for button Visibility To matches #6

m0agh opened this issue Oct 14, 2017 · 6 comments

Comments

@m0agh
Copy link

m0agh commented Oct 14, 2017

Guys, I've added your tool to my test, I need my test waits and checks Visibility of 2 buttons then finishes the test.

In my test:

ConditionWatcher.waitForCondition(new BtnSendVerificationInstruction());
onView(withId(R.id.btnSendVerification)).perform(click());`

The Instruction:

public class BtnSendVerificationInstruction extends Instruction {
@Override
public String getDescription() {
    return "BtnSendVerification should be moved to center of activity";
}

@Override
public boolean checkCondition() {
    Activity activity = ((TestApplication)
            InstrumentationRegistry.getTargetContext().getApplicationContext()).getCurrentActivity();
    if (activity == null) return false;

    Button btnSendVerification = (Button) activity.findViewById(R.id.btnSendVerification);
    boolean isDisplayed = btnSendVerification.isShown();
    return btnSendVerification != null && isDisplayed;
}
}

Any idea?
Thanks

@FisherKK
Copy link
Collaborator

FisherKK commented Oct 14, 2017

Hello @MortezaAghili can you specify what do you expect to achieve or what is not working? I am a little bit lost.

  1. You said that you wait for 2 buttons, but your Instruction is waiting only for one button with R.id.btnSendVerification.
  2. You said that you wait for Button Visibility parameter but you check isShown instead. Maybe you wanted this:
return btnSendVerification != null && btnSendVerification.getVisibility() == View.VISIBLE;

Furthermore Visibility is a tricky thing because it's just parameter. Your Button can have Visibility set to Visible but it might be inside container which Visibility is set to Gone. That's why instead of checking Button is Visible it would be better to check if all it's parents have also Visibility set to Visible:

    public static boolean isVisible(int viewId, Activity activity) {
        if (activity != null) {
            View v = activity.findViewById(viewId);
            if (v != null) {
                return isVisible(v);
            }
        }
        return false;
    }

    public static boolean isVisible(View v) {
        if (v.getVisibility() != View.VISIBLE) {
            return false;
        }

        if (v.getParent() != null && v.getParent() instanceof View) {
            View parent = (View) v.getParent();
            return isVisible(parent);
        }
        return true;
    }

Another tricky thing is - your Button Visibility parameter might be set to Visible but if you scroll down your screen and it's not visible to your eye anymore it still has Visibility parameter set to Visible. So maybe in this case you would rather check if the View is fully Rendered on your screen instead of just Visibility parameter?

Method below calculates View's area and 90% of it must be currently visible to your eye on the screen:

    public static boolean isFullyRendered(int viewId, Activity activity) {
        if (activity != null) {
            View v = activity.findViewById(viewId);
            if (v != null) {
                return v.getGlobalVisibleRect(new Rect())
                    && isDisplayingAtLeast(90).matches(v)
                    && withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE).matches(v);
            }
        }
        return false;
    }

It is a bad idea to hardcode R.id in your instruction. Because then it can be used only for one specific View with hardcoded R.id.

You could rewrite it to:

public class ViewVisibleInstruction extends Instruction {
    @Override
    public String getDescription() {
        int resourceId = getDataContainer().getInt(Instruction.KEY_INT_RESOURCE_ID);
        return "View with id(" + resourceId + ") should be visible";
    }

     @Override
     public boolean checkCondition() {
         int resourceId = getDataContainer().getInt(Instruction.KEY_INT_RESOURCE_ID);
         return isVisible(resourceId, getCurrentActivity());
     }
}

And use it like that:

    public static final int VIEW_INIT_TIMEOUT = 1000 * 10;

    public void waitForVisible(int resourceId) throws Exception {
        Bundle data = new Bundle();
        data.putInt(Instruction.KEY_INT_RESOURCE_ID, resourceId);

        ViewVisibleInstruction viewVisibleInstruction = new ViewVisibleInstruction()
        viewVisibleInstruction.setData(data);

        ConditionWatcher.setTimeoutLimit(VIEW_INIT_TIMEOUT);
        ConditionWatcher.waitForCondition(viewVisibleInstruction);
    }
waitForVisible(R.id.btnSendVerification);
onView(withId(R.id.btnSendVerification)).perform(click());

@m0agh
Copy link
Author

m0agh commented Oct 15, 2017

@FisherKK Thank you so much for lighting the right path.

@m0agh m0agh closed this as completed Oct 16, 2017
@m0agh m0agh reopened this Oct 23, 2017
@m0agh
Copy link
Author

m0agh commented Oct 23, 2017

I'm wonder how can i call a method when timeout is happened and the test stopped ?

java.lang.Exception: BtnSendVerification should be moved to center of activity - took more than 1000 seconds. Test stopped.

@FisherKK
Copy link
Collaborator

FisherKK commented Oct 23, 2017

But if your view didn't appear for over 1000 seconds (how about keeping it smaller, there is no need to set it to such big value in my opinion) then doesn't that mean something is wrong and it should simply crash your test?

If you really want to avoid crash:

public void waitAndAvoidCrash(Instruction instruction) {
    try {
        ConditionWatcher.waitForCondition(instruction);
    } catch (Exception e) {
        // ignore or do something
    }
}

@m0agh
Copy link
Author

m0agh commented Oct 23, 2017

@FisherKK Thanks for you response, this timeout important for our test because we need to send and get a few text messages from the operator. Sometimes this changing information take a few minutes.

Where i need implement this method?

@FisherKK
Copy link
Collaborator

FisherKK commented Oct 23, 2017

It's up to you I guess.

   try {
        waitForVisible(R.id.btnSendVerification);
    } catch (Exception e) {
        sendSomeLogs();
        throw e;
    }
    onView(withId(R.id.btnSendVerification)).perform(click());

If you always are logging the same thing then you can make it cleaner by combining try/catch with waitForVisible method.

    public static final int VIEW_INIT_TIMEOUT=1000*10;
    
    public void waitForVisible(int resourceId, boolean shouldLogSomeStuff) throws Exception {
        Bundle data = new Bundle();
        data.putInt(Instruction.KEY_INT_RESOURCE_ID,resourceId);
    
        ViewVisibleInstruction viewVisibleInstruction = new ViewVisibleInstruction()
        viewVisibleInstruction.setData(data);
    
        ConditionWatcher.setTimeoutLimit(VIEW_INIT_TIMEOUT);
    
        try{
            ConditionWatcher.waitForCondition(viewVisibleInstruction);
        } catch (Exception e) {
            if shouldLogSomeStuff {
                sendSomeLogs();
            }
            throw e;
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants