I wrote an Espresso test that writes some text to a TextView, performs an action and then checks whether the text in the TextView is still the same.
The test fails on one of the test devices (Huawei P20, Android 8.1.0) because the entered text is auto-corrected (from 1234 5678 to 12th 5678). And this fails my test. The text is not auto-corrected when I manually enter the same numbers.
This is how I input the text in my Espresso test:
onView(withId(R.id.reference_value))
.perform(scrollTo(), click())
.check(matches(isDisplayed()))
.perform(typeText("1234 5678"));
closeSoftKeyboard();
I know I could just change the input text to something that won't be auto-corrected. But I would like to have a solution that generally makes sure that the entered text is not modified to something else. Ideally without having to manually change the configuration of my test device.
Do any of you guys have an idea how I could accomplish this?
One way that works for me is to use replaceText() instead, although this still seems to be a bit of a hack.
Another option may be to disable autocorrect through some Android API call or manually through the UI like for animations.
This issue occurs with some keyboards. For me it was with Microsoft SwiftKey Keyboard was set as my default keyboard.
Two solutions that worked for me:
Solution 1: Change input type to TYPE_TEXT_FLAG_NO_SUGGESTIONS during test if using typeText which disables the suggestions & corrections for that view.
Example:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class HelloWorldEspressoTest {
#Rule
public ActivityScenarioRule<MainActivity> activityScenarioRule
= new ActivityScenarioRule<>(MainActivity.class);
#Before
public void setUp() {
// get the view & set the input type
activityScenarioRule.getScenario().onActivity(activity ->
((EditText) activity.findViewById(R.id.etHello))
.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS));
}
#Test
public void testText() {
onView(withId(R.id.etHello)).perform(typeText("Smoth go"));
onView((withId(R.id.etHello))).check(matches(withText("Smoth go")));
}
}
Solution 2: Using my own ViewAction
Helper.java -> This file is placed inside the same test package with test.
public class Helper {
public static ViewAction setTextInEt(final String value){
return new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return allOf(isDisplayed(), isAssignableFrom(EditText.class));
}
#Override
public void perform(UiController uiController, View view) {
((EditText) view).setText(value);
}
#Override
public String getDescription() {
return "set text";
}
};
}
}
Test class:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class HelloWorldEspressoTest {
#Rule
public ActivityScenarioRule<MainActivity> activityScenarioRule
= new ActivityScenarioRule<>(MainActivity.class);
#Test
public void testText() {
onView(withId(R.id.etHello)).perform(Helper.setTextInEt("Smoth go"));
onView((withId(R.id.etHello))).check(matches(withText("Smoth go")));
}
}
So far solution 2 has worked very well for me. Before that smoth text was auto-corrected to smooth every time.
This can be done for the TextView as well just replace EditText to TextView in helper method.
Related
I have experienced next scenario:
each test in my test-project uses separate activity
test1 (main screen) belong to activity#1, there is a button on this screen which can change state from STATE#1 (OFF) to STATE#2 (ON)
tap on button STATE#1 (from test1) cause raise of another screen which belong to activity#2 (there is test2 starting), then on act.#2 user perform some actions which should change button state to STATE#2
but in my test button STATE#2 wouldn't be refreshed and passed to the previous the activity#1
Do I need to sync test data in specific way? If button's state (which is on activity#1) can be changed from another test activity (activity#2)
Here is example of what I'm doing:
First of all test after which button state should be changed
TEST2 - test that should change button1 state
#RunWith(AndroidJUnit4.class)
public class ChangeBtnState extends MyIdle
{
#Rule
public ActivityTestRule<Activity#2> EspressoTestRule#2 = new ActivityTestRule<>(Activity#2.class, false, true);
private MyIdle IdlingRecourseActivity#2;
#Before
public void SetUpTest2()
{
EspressoTestRule#2.getActivity().getSupportFragmentManager().beginTransaction();
IdlingRecourseActivity#2 = (ESP_idling) EspressoTestRule#2.getActivity().getIdlingResource();
Espresso.registerIdlingResources(IdlingRecourseActivity#2);
}
#Test
public void StartTestRule2()
{
// Just some actions on activity#2 after which button1 state should be from STATE#1 to STATE#2
ViewInteraction ButtonSendtoOFF = Espresso.onView(allOf(ViewMatchers.withId(android.R.id.SomebuttonONact#2))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
ButtonSendtoOFF.preform(click()); // here click
}
}
#After
public void unregistereSetUpTest2()
{
if (IdlingRecourseActivity#2 != null)
{
Espresso.unregisterIdlingResources(IdlingRecourseActivity#2);
}
}
}
Now checking does button state changed on activity#1
#RunWith(AndroidJUnit4.class)
public class CheckBtnState extends MyIdle
{
#Rule
public ActivityTestRule<Activity#1> EspressoTestRule#1 = new ActivityTestRule<>(Activity#1.class, false, true);
private MyIdle IdlingRecourseActivity#1;
#Before
public void SetUpTest1()
{
EspressoTestRule#1.getActivity().getSupportFragmentManager().beginTransaction();
IdlingRecourseActivity#1 = (ESP_idling) EspressoTestRule#1.getActivity().getIdlingResource();
Espresso.registerIdlingResources(IdlingRecourseActivity#1);
}
#Test
public void StartTestRule1()
{
// Check some actions FROM activity#2 to change **button1** from STATE#1 to STATE#2
ViewInteraction ButtonSTATE = Espresso.onView(allOf(ViewMatchers.withId(android.R.id.Button1), withText("OFF"))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
//ButtonSTATE.preform(click()); // here click
}
#After
public void unregistereSetUpTest1()
{
if (IdlingRecourseActivity#1 != null)
{
Espresso.unregisterIdlingResources(IdlingRecourseActivity#1);
}
}
}
BTW, I have look through a lot of topics and even found examples with intents, but it doesn't work for me, perhaps there is should be specific structure.
If you need extra info please add your question in comments.
The problem is that you are writing two tests such that the second test relies on the state of the first one. This is incorrect test design. Every test should be able to run completely independent of any other tests. You need to rethink what each of your tests are testing. What happens in the second activity that affects the state of the first one? Are you using startActivityForResult()? If so, your test for the second activity should verify that the expected result is set from the second activity. This test should not rely on anything in the first activity.
I need to write a UI test to validate that clicking the floating action button results in displaying the SecondActivity.
public class MainActivityTest {
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
#Before
public void setUp() throws Exception {
}
#Test
public void onClick() throws Exception {
onView(withId(R.id.button)).perform(click());
}
}
Is it enough?
And how can I validate that the Activity properly displays the text contents of an incoming object: name, age, phone number?
I have just started using espresso(
Is it enough?
No, it is not enough. This code
onView(withId(R.id.button)).perform(click()); only performs a click on the button, but there is nothing that verifies that the application behaved correctly after that.
To verify that an intent to open the SecondActivity was created, you need to use Espresso Intents.
how can I validate that the Activity properly displays the text contents of an incoming object
You can use something like:
onView(withId(R.id.textView)).check(matches(withText("Expected text")));
Take a look at the Espresso Cheatsheet for more.
I'm new to the automated testing, and using espresso to test my android App.
The problem is that I have multiple dynamic views depending on certain conditions :
My user has a boolean attribute, let's call it "isPremium"
when I click on a button my user is redirected to FragmentA if isPremuim == true , else he's redirected to FragmentB.
now for my tests I have
#Test public void testFragmentA();
and
#Test public void testFragmentB();
but when I run my tests based on my data, forcibly one of the two tests fails.
so should i make one test for both fragments like
private void testFragmentA();
private void testFragmentB();
#Test
public void myGlobalTest {
if(user.isPremium) testFragmentA();
else testFragmentB();
}
is this the right way to make my tests ? or there is another better way, because sincerly I'm not convinced with this method.
It would be best if you set value for premium at the beginning of each test (true for testFragmentA, false for testFragmentB). That way you will know what you are expecting and what each fragment depends on.
Also, if user is some global variable, you should keep its state in #Before and restore it in #After method.
boolean isPremium;
#Before
public void init() {
isPremium = User.isPremium();
}
#Test
public void testFragmentA(){
User.setPremium(true);
// test fragment A
}
#Test
public void testFragmentB(){
User.setPremium(false);
// test fragment B
}
#After
public void restore() {
User.setPremium(isPremium);
}
I want to know how to set an item in a spinner in espresso testing.
onView(withId(R.id.spinner_gender)).perform(click());
onData(allOf(is(instanceOf(String.class)))).atPosition(0).perform(click());
This code above does not work :/
Your code snippet looks correct, so there may be an issue with another part of your test class?
Are you getting an Exception or stack-trace you can update your question with? Also check the espresso documentation for a bit more explaination.
See small code example of how you can select a spinner option by text or it's position.
#RunWith(AndroidJUnit4.class)
public class BasicEspressoTest {
#Rule
public ActivityTestRule<MainActivity> testRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void selectBySpinnerPosition() throws Exception {
onView(withId(R.id.spinner)).perform(click());
onData(allOf(is(instanceOf(String.class)))).atPosition(0).perform(click());
}
#Test
public void selectBySpinnerText() throws Exception {
onView(withId(R.id.spinner)).perform(click());
onData(allOf(is(instanceOf(String.class)), is("spinner's text"))).perform(click());
}
}
JUnit library has an Assume.* instructions like Assume.assumeTrue(boolean) which works like assertions, but not cause test to fail and just to been ignored.
I want to perform such checking in arrange part of test for one of my views, by example assume, that founded checkbox is checked before starting the act part of test.
Take a look:
#Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void deselectFilter_AllFiltersSelected_CheckboxAllSelectedUnchecked() {
//arrange
ViewInteraction checkBox = onView(
allOf(withId(R.id.cbCheckAll), isDisplayed()));
//assume that this checkbox is checked
//act
...
//assert
...
}
In the arrange part i've received not a View, but ViewInteraction.
So I can perform such assertion like checkBox.check(matches(isChecked()))
But how to perform assume?
You could write a custom ViewAssertion to assume that no Exception is thrown when Espresso ViewMatcher fails:
public static ViewAssertion assume(final Matcher<? super View> viewMatcher) {
return new ViewAssertion() {
#Override
public void check(final View view, final NoMatchingViewException noViewFoundException) {
try {
ViewAssertions.matches(viewMatcher).check(view, noViewFoundException);
} catch (Throwable e) {
// Assume that there is no exception
Assume.assumeNoException(e);
}
}
};
}
Then you can use that assertion to assume like:
onView(withId(R.id.cbCheckAll)).check(assume(isChecked()));
The only way i've founded at this moment is just finding assuming view manually with activity from test rule. And then assume via jUnit.
CheckBox checkBox = (CheckBox) mActivityTestRule.getActivity().findViewById(R.id.cbCheckAll);
Assume.assumeTrue(checkBox.isChecked());
If you know a better way, maybe with using Espresso, please answer. Seems that it impossible to access view directly from Espresso commands