I am new to Espresso. That is why this could be more of a "should this even work or am I just doing it wrong" type of question.
But basically I have issues testing my WebView with Espresso. When I arrive to the Activity (actually Fragment) with the WebView, Espresso cannot determine that the view is idle and I get the dreaded AppNotIdleException exception. And I don't know if I am doing something wrong or should this case even work.
Here's my test basically:
#Test
public void testIncludingWebView() {
// Go to the activity we want to test. Note, tests start actually earlier.
onView(allOf(withId(R.id.action_open_activity1), isCompletelyDisplayed()))
.perform(click());
// We arrived at the right Activity.
assertEquals(Activity1.class, Utils.getCurrentActivity().getClass());
// Select an item from a GridView in the Activity that will launch the WebView.
onData(MyMatcher.withName(is("Dummy")))
.perform(click());
// We should arrive to an Activity with a Fragment. The Fragment will
// display a full screen DialogFragment that contains the WebView.
assertEquals(WebViewActivity.class, Utils.getCurrentActivity().getClass()); // <== We never assert this
// Some tests on the WebView. But these are never run.
onWebView().forceJavascriptEnabled();
onWebView().withElement(findElement(Locator.NAME, "name"))
.perform(DriverAtoms.webKeys("some text"))
.withElement(findElement(Locator.XPATH, "//button[contains(text(), 'Submit')]"))
.perform(webClick());
}
To recap: I have some Activities that I navigate to and finally I arrive to an Activity with a Fragment and this Fragment will display a full screen DialogFragment that has the WebView. When I arrive here, Espresso cannot determine the idle state.
Also very important to mention is that when I enable Developer Options -> Show screen updates I can see the screen constantly flickering on the view where I get the timeout exception meaning that the screen is updated constantly. AFAIK one of the things Espresso does for determining if the app is idle or not is to wait for the screen rendering to end. So this could be the issue.
But why would the screen update all the time? And I guess here's the beef of it; is it Chrome / WebView updating the screen all the time because of how I open the WebView or do I have some unknown animation going on that I just need to find? I tried to look for animations in the view that I can stop, but since it's just a WebView I am displaying, I haven't found anything.
So have others been able to test this kind of scenario with Espresso or not?
Related
I have an activity in an Android application that includes both native UI elements and a WebView. The WebView is normally hidden (Visibility.GONE), and it performs occasional work "in the background" on its JavaScript loop. On a particular button click, the WebView is made visible and the user can see the web page. On a subsequent button click, the WebView is hidden again. The user can repeat this process.
I added a call to WebView.postVisualStateCallback when the user presses the button to make the WebView visible. I only make the WebView visible during the VisualStateCallback.onComplete. This works great the first time the user clicks the button and I make the WebView visible. However, after the user dismisses the WebView and I make it hidden again, I cannot repeat this process. Any subsequent call to WebView.postVisualStateCallback never results in another call to VisualStateCallback.onComplete.
mWebView.postVisualStateCallback(++mWebViewVisualStateCallback, new WebView.VisualStateCallback() {
#Override
public void onComplete(long requestId) {
if (requestId == mWebViewVisualStateCallback) {
mWebView.setVisibility(View.VISIBLE);
}
}
});
I am not navigating to new web pages in the WebView. The web page is essentially a long-lived single-page app. The DOM may be changing in the WebView while it is in the background, which is why I'm trying to subsequently call WebView.postVisualStateCallback. I am testing on an emulator and Pixel 2 with Oreo 8.1/API 27.
Is it possible to use WebView.postVisualStateCallback multiple times on the same main frame? Why would WebView.postVisualStateCallback only work once?
Quoted from android website, I think you might need to consider these situations when using postVisualStateCallback()
To guarantee that the WebView will successfully render the first frame
after the VisualStateCallback#onComplete method has been called a set
of conditions must be met:
If the WebView's visibility is set to VISIBLE then the WebView must be attached to the view hierarchy.
If the WebView's visibility is set to INVISIBLE then the WebView must be attached to the view hierarchy and must be made VISIBLE from
the VisualStateCallback#onComplete method.
If the WebView's visibility is set to GONE then the WebView must be attached to the view hierarchy and its LayoutParams's width and height
need to be set to fixed values and must be made VISIBLE from the
VisualStateCallback#onComplete method.
Hope this is helpful~
in my app I have a fragment using the dm77/barcodescanner.
I'm working on a shop app, ex: you scan an item -> page of product -> continue shopping -> open another scanner sessione -> etc.
Now, as you can see, because the clinet wants to maintain the back stack, multiple istances of that fragment could be open (but not at the same time).
The first fragment with the scanner works well, the others has a still image, even if I call
scannerView.stopCamera();
scannerView = null;
leaving the first fragment.
NOTE: the other fragment works well if, instead of add we use replace. But that will create problems on the fragment's animations.
After furious debugging, I found how to make all working:
When the fragment is visible / in use, create and add programmatically to your layout the ZXingScannerView, starting the camera and preview;
When the fragment is not visible / used, remove the ZXingScannerView from your layout;
On some android version (like 5.01) be careful to not call startCamera more than one time, otherwise you'll not be able to open it anymore until you restart the app. The error here is caused by dead thread exception.
Hope to have been enough clear and helpful.
I've got an xamarin forms/prism app, and my hardware back button does nothing on the initial page.
If I navigate to another page, it closes the app as expected. If I navigate to the initial page again, it also closes the app - but not if the app just started.
Is there something I'm missing?
My class App mainly has an OnInitialized that navigates to the initial page:
protected override void OnInitialized()
{
NavigationService.NavigateAsync( "MyMasterDetail/MyNavigationPage/StartPage", animated: false );
}
On MyMasterDetail, there are buttons that navigate to MyNavigationPage/SettingsPage and other pages like that.
It doesn't matter if I use Android 5 in Emulator or Android 6 on a real device, the behaviour is the same.
When using a MasterDetail as your root, you are not actually navigating anywhere else. You are simply changing the Detail property of the MasterDetail to another Page. This is not a navigation action. So you are not really navigating. If you want to fake it, you need to add the INavigationPageOptions to your MyNavigationPage and set the ClearNavigationStackOnNavigation property to false. This will continuously push new pages onto the MasterDetailPage.Detail MyNavigationPage without clearing the stack (PopToRoot). Then this will allow your bac button to behave like you are wanting.
I am catering for two specific scenarios
Ensure user is forced to Login if the app was running when device is
locked and then then unlock it
When the user logs out unwind the back stack and take them to the login page
As I need to customise with how Views are loaded I have implemented a Custom View Presenter (MvxAndroidViewPresenter) see SplitViewPresenter presentation
For scenario 2 I pass some PresentationValues in the Request to signal that I want to do something different when showing the LoginView
if (request.PresentationValues != null && request.PresentationValues.ContainsKey(MakeViewTopHint.HintName))
{
intent.AddFlags(ActivityFlags.NewTask);
intent.AddFlags(ActivityFlags.ClearTask);
}
This causes the the stack do be unwound and the LoginView is shown. Great.
For scenario 1 I use a BroadcastReceiver to catch the Intent, ActionScreenOff. Then I try to show the same Activity that was previously shown with a NewTask and ClearTask.
Scenario 1 works fine if I remove the NewTask and ClearTask flags used for Scenario 2 but I need them to clear the back stack down.
Does anyone know why the use of NewTask and ClearTask would be preventing the Activity from being shown subsequently
The problem was MVVMCross's MvxAndroidViewsContainer.AdjustIntentForPresentation. It sets the NewTask ActivityFlag on the Intent that is used for Scenario 1.
In fairness there is probably a good reason for this but when NewTask is used to start an Activity after previously starting that Activity with NewTask and ClearTask it will not resolve and the Activity is not displayed.
Thanks to MVVMCross' almost limitless configurability I am able to create a custom AndroidViewsContainer. When Scenario 1 is being played out I ensure that the NewTask flag is not used and everything works.
The issue of ActivityFlags.NewTask preventing the Activity from being resolved is not an MVVM cross or Xamarin thing as I can now repro, what I consider to be odd behavior, in vanilla Android.
I now have a working solution to both scenarios
Force user to Login after a device Lock/Unlock
Unwind the back stack and send user to Login when they log out
I have an activity, DogActivity, with a slider. When the user slides view PawsView to a certain degree, I start another activity, CatActivity, using startActivity(intent). If the user clicks the back button, normally the user returns to DogActivity. Here is my problem: if in Developer options I set Do not keep activities then when the user clicks the back button and thus returns to DogActivity, the slider is not asserted and so PawsView is back to its original position; however, if I don't have that option selected, upon returning to DogActivity the slider is still asserted (the sliding already occurred).
Since I don't want to depend on the user selecting or deselecting Do not keep activities, I need to do this programmatically. So does anyone know how to do this? I have tried putting the appropriate code inside onResume but that has no effect. It's as if finishing CatActivity has no effect on DogActivity. BTW, the view I am using to display PawsView is a custom view.
I already tried using a handler with postDelayed to pull PawsView back to normal, but the handler always executes before the startActivity is executed. If on the other hand I start a proper Thread to run the call to close the slider, I get an error about the wrong thread trying to change the layout.
Another way of asking the question may be: How do I force onResume to be called even when Do not keep activities is NOT selected on a user's device.
You could try to launch CatActivity using startActivityForResult and then handle the result in onActivityResult and do the necessary setup from there. It's sort of like forcing onResume.