Wait for button Visibility To matches
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
Hello @MortezaAghili can you specify what do you expect to achieve or what is not working? I am a little bit lost.
- You said that you wait for 2 buttons, but your Instruction is waiting only for one button with
R.id.btnSendVerification. - 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());
@FisherKK Thank you so much for lighting the right path.
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.
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
}
}
@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?
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;
}
}