UIAutomator - Screenshot after a failure - android

I'm new in Android Testing. I'm using UIAutomator to make some Black-Box testing. I would like to take a screenshot each time a test fails. I tried to use the TestWatcher class by adding a Rule in my test class. But, it doesn't work. I guess we cannot use the #Rule annotation when using UIAutomator.
There are lots of related topics for Espresso but I didn't find anything for UIAutomator.
Does anyone have a method to solve my issue?
Thanks!

Yes, you can do it in this way:
#RunWith(AndroidJUnit4.class)
public class TestClass {
#Rule
public ScreenshotTakingRule rule = new ScreenshotTakingRule();
#Test
public void yourTest() {
}
}
and
/**
* Junit rule that takes a screenshot when a test fails.
*/
public class ScreenshotTakingRule extends TestWatcher {
#Override
protected void failed(Throwable e, Description description) {
Log.d(TAG, "taking screenshot..." + description.getMethodName());
takeScreenshot(description.getMethodName());
}
private void takeScreenshot(String screenShotName) {
ScreenCapture screenCapture = Screenshot.capture();
try {
screenCapture.setName(screenShotName);
Set<ScreenCaptureProcessor> processorSet = new HashSet<>();
processorSet.add(new SimpleNameScreenCaptureProcessor());
screenCapture.process(processorSet);
} catch (IOException ex) {
Log.d(TAG, "error while taking screenshot " + ex.getMessage());
}
}
}

Related

Interprocess or adb communication with AndroidJUnit

I want to know, does any way exist to communicate with system during instrumentation test execution.
For example:
I have a phone with IR port on onboard & I can work with it through private SDK, also I can tune it with my application. In my Instrumentation test cases I want test app behavior based on external events which I want to configure before test separate test execution.
It's looks like
#Test
public void test() throws Exception {
setupExternalCondition(condition1_ON); // setup external transiver
assertNotNull(IR.read());
assertTrue(assertIR.write());
setupExternalCondition(condition1_OFF);
assertNotNull(IR.read());
assertFalse(IR.write());
}
It's very simple example but there is a lot of "conditions", and sdk updating frequencies to high. I can't do all of this verification manually, and can't ask "transiver&SDK team" make a mock states list for writing just a unit test for coverage. So I want somehow inject external component execution to TestRuner for receiving events(or testName before test case execution) on local machine(or CI machine) to setup external condition.
Simple solution(I think) to run a tcp server on appUnderTest and request external condition change - I am not sure does it possible, and not sure about stable connection(wifi), so may be it's possible to do over adb.
Any suggestions?
P.S: test device has root permissions.
So, find not bad but not ideal solution.
Still wait for better proposition, if not may be this answer will be helpful for someone;
For "building the bridge" between local machine and AndroidJUnitTest I add next class to tests:
class IPCServiceBridge extends BroadcastReceiver {
private static final String FILTER_ID = "IPC_SERVICE";
private static IPCServiceBridge sInstance;
private boolean mIsPermitted;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("ipc.service.action")) {
mIsPermitted = true;
}
}
public static IPCServiceBridge getInstance() {
if (sInstance == null) {
sInstance = new IPCServiceBridge();
IntentFilter filter = new IntentFilter();
filter.addAction("ipc.service.action");
Context context = InstrumentationRegistry.getContext();
context.registerReceiver(sInstance, filter);
}
return sInstance;
}
public void sendIpcCommand(String commandName) {
try {
int i = 30;
mIsPermitted = false;
while (i > 0) {
pub("request:" + commandName);
Thread.sleep(1000);
if (mIsPermitted) {
break;
}
i--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!mIsPermitted) {
throw new RuntimeException("IPC service does not respond");
}
}
private static void pub(String msg) {
Log.e(FILTER_ID, msg);
}
}
Than I start adb logcat -s "filter_name", parse and check which condition should be applied for InsttUnit test. When conditions is ready i send back broadcast receiver with required action.
#Test
public void test2() throws Exception {
IPCServiceBridge.getInstance().sendIpcCommand("CONDITION#123");
}
Work good, but I'm not sure that it will be super stable.

Error utilizing RealmDB while unit testing

I am writing a test for a ViewModel. The function in the ViewModel is this:
public void discoverMovies(boolean showLoading) {
// reset the states to initial states
moviesLoading.set(showLoading);
errorViewShowing.set(false);
emptyViewShowing.set(false);
mMoviesRepository.getPopularMovies(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableObserver<List<Movie>>() {
#Override
public void onNext(List<Movie> value) {
// show or hide empty view
boolean isEmpty = value == null || value.isEmpty();
if (!isEmpty) {
saveResponse(value);
movies.clear();
movies.addAll(value);
}
emptyViewShowing.set(isEmpty);
}
#Override
public void onError(Throwable throwable) {
errorViewShowing.set(true);
moviesLoading.set(false);
emptyViewShowing.set(false);
errorString.set(getErrorMessage(throwable));
}
#Override
public void onComplete() {
moviesLoading.set(false);
errorViewShowing.set(false);
}
});
}
private void saveResponse(final MovieResponse mainResponse) {
Realm.getDefaultInstance().executeTransaction(new Realm.Transaction() {
#Override public void execute(Realm realm) {
RealmMovie realmMovie = realm.createObject(RealmMovie.class);
realmMovie.setId(1);
realmMovie.setMarvelResponse(new Gson().toJson(mainResponse));
}
});
}
And I test the function above in my test class like this:
Note: Everything works without the Realm aspect. I've confirmed that.
#Test
public void getPopularMoviesWithoutError() {
// given the following movies
when(mMoviesRepository.getPopularMovies(PAGE)).thenReturn(Observable.just(MOVIES));
// discover popular movies
mMoviesViewModel.discoverMovies(true);
// verify that the repository is called
verify(mMoviesRepository).getPopularMovies(PAGE);
// test that the loading indicator is hidden
assertFalse(mMoviesViewModel.moviesLoading.get());
// check that the empty view is hidden
assertFalse(mMoviesViewModel.emptyViewShowing.get());
// check that the error view is hidden
assertFalse(mMoviesViewModel.errorViewShowing.get());
assertTrue(mMoviesViewModel.movies.size() == MOVIES.size());
}
And it keeps on giving me java.lang.IllegalStateException: CallRealm.init(Context)before calling this method. How can I initialize Realm
to be available
I think the error message you are getting is quite clear about what is causing the problem. You are not calling Realm.init.
There are several ways of doing this. The simplest is the #Before and #After annotations on the test suite. You could also use a TestRule
Unfortunately, Realm.init requires a Context. To get that context, you are going to have to be in some environment that has one. That means that you will either have to run your tests on a device, as Instrumentation tests or, as #David Rawson suggests, use Robolectric.

Android Local Tests vs Instrumented Test

I'm trying to check if my API is available within a unit test, to be sure that it responds with 200.
Now, my problem is that I'm not really sure when to use Local Test and when I have to use Android Instrumentation Tests. I know that I have to use Instrumented Tests for UI testing but how to test the endpoint?
I use Retrofit2 for communication. And tried to test Endpoint in 2 ways with Local Tests.
Example 1 (synchronous, does not work)
public class EndpointTest {
EndpointApi api;
SimpleInjection simpleInjection;
#Before
public void setUp() {
simpleInjection = new SimpleInjection();
api = simpleInjection.getEndpointApi();
}
#Test
public void endpoint_1_isAvailable() {
Call<ApiResponse> rootCall = api.getRoot();
try {
int reponseCode = rootCall.execute().code();
Assert.assertEquals(200, reponseCode);
} catch (IOException e) {
Assert.fail();
}
}
}
Example 2 (asynchronous, does work)
public class EndpointTest {
EndpointApi api;
SimpleInjection simpleInjection;
#Before
public void setUp() {
simpleInjection = new SimpleInjection();
api = simpleInjection.getEndpointApi();
}
#Test
public void endpoint_2_isAvailable() {
Call<ApiResponse> rootCall = api.getRoot();
rootCall.enqueue(new Callback<ApiResponse>() {
#Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
Assert.assertEquals(200, response.code());
}
#Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Assert.fail();
}
});
}
}
Do I have to use Android Instrumentation Test for asynchronous mode?
The decision of whether to run your tests on a local JVM on your development machine or on an Android device/emulator is not based on whether your code is synchronous or not. Usually you would only want to run unit tests locally, as it's a lot faster and allows you to use TDD. Your tests do network requests, so they're not unit tests per say, since they have the dependency on the server - they are integration tests. It's preferable to run integration tests on an Android device to get better feedback.

Several method test issue - hang on second test- Robotium with ActivityInstrumentationTestCase2

What I'm doing: I'm testing an Android aplication using Robotium.
What works: If I have a test in one test method everithing works fine
What doesn't work: If I try do divide this test in to two smaller, then the first test is passing, and the second is hanging (or freezing - I don't know how to name that)
Why I need that I need that to make reports in sppon showing bars for each test (I will have: testLogin, testAddCustomer, testLogout etc). Example of spoon report looks like this:
If i have one big test (testAll) there is only one big green bar, but I need to have many short bars for each test method like on the image above.
What I've done: I have read a lot of different topics about simillar problems but it didn't help me
Here is an short example of what works (one method - testAll()) I wrote currents activities in comments:
public class LogInLogOut extends ActivityInstrumentationTestCase2 {
private Solo solo;
private static final String LAUNCHER_ACTIVITY_FULL_CLASSNAME = "mobile.touch.core.activity.SplashScreenActivity";
private static Class<?> launcherActivityClass;
static {
try {
launcherActivityClass = Class.forName(LAUNCHER_ACTIVITY_FULL_CLASSNAME);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public LogInLogOut() throws ClassNotFoundException {
super(launcherActivityClass);
}
public void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation());
getActivity();
}
#Override
protected void tearDown() throws Exception {
try {
solo.finalize();
} catch (Throwable e) {
e.printStackTrace();
}
getActivity().finish();
super.tearDown();
}
public void testAll() {
// here is LoginActivity <<-----
// username
solo.clickOnView(solo.getView(0x3));
solo.enterText((android.widget.EditText) solo.getView(0x3), "user");
// enter password
solo.clickOnView(solo.getView(0x3, 1));
solo.enterText((android.widget.EditText) solo.getView(0x3, 1), "password");
// click on log in button
solo.clickOnView(solo.getView(android.widget.Button.class, 0));
// here ContainerActivity starts <<-----
//click on log out
solo.clickOnMenuItem("LogOut");
}
}
With the testAll () method all test is passed. But I need to divide that into testLogin() and test testLogout().
here is how I divide metod testAll into two smaller (testLogin() & testLogout()):
public void testLogin() {
// here is LoginActivity <<-----
// username
solo.clickOnView(solo.getView(0x3));
solo.enterText((android.widget.EditText) solo.getView(0x3), "user");
// enter password
solo.clickOnView(solo.getView(0x3, 1));
solo.enterText((android.widget.EditText) solo.getView(0x3, 1), "password");
// click on log in button
solo.clickOnView(solo.getView(android.widget.Button.class, 0));
// here ContainerActivity starts <<-----
}
public void testOut() {
//click on log out
solo.clickOnMenuItem("LogOut");
}
Now first test (testLogin()) is passed and the second (testLogout()) is hanging
To check if the test even started I put log into it.
public void testOut() {
Log.i("checkTestB", "test B started"); <<-- here is the log
//click on log out
solo.clickOnMenuItem("LogOut");
}
It occurse that the testLog is not ececuting the code, becouse "test B started" was not in the log
Question: How can I solve that problem?
I find answer. Method setUp() is perormed before each test method, and tearDown() is performed after each test method. It coused the problem.
I override setUp() and tearDown() with:
#Override
public void setUp() {
}
#Override
protected void tearDown() throws Exception {
if (exit == true) {
try {
solo.finalize();
} catch (Throwable e) {
e.printStackTrace();
}
solo.finishOpenedActivities();
super.tearDown();
}
}
Then I wrote my own mehtods startUp() identical like setUp() and tearDown() deleted.
public void startUp() {
solo = new Solo(getInstrumentation());
getActivity();
}
Then setUp() do nothig when it is performed before test. The same thing is with teadDown() it makes nothing unltil I will set exit==true in test method.
The resolution is to start manulay startUp() in FIRST test method and in the LAST test method exit=ture (default is false), then tearDown() will run and close everything.
Now I manually start activity in testLogin():
public void testLogin() {
startUp(); <<<<<<<-----------------same method as setUp() but can started manually
solo.clickOnView(solo.getView(0x3));
solo.enterText((android.widget.EditText) solo.getView(0x3), "user");
solo.clickOnView(solo.getView(0x3, 1));
solo.enterText((android.widget.EditText) solo.getView(0x3, 1), "password");
solo.clickOnView(solo.getView(android.widget.Button.class, 0));
}
In the second methods testLogout() I use on the begginig: solo = new Solo(getInstrumentation()); and turnDown() method on the end
public void testLogOut() {
solo = new Solo(getInstrumentation());
solo.clickOnMenuItem("LogOut");
exit=true;
}

Mock Retrofit Observable<T> response in Android Unit tests

I have an API interface and I'm testing a View that involves network calls.
#Config(emulateSdk = 18)
public class SampleViewTest extends RobolectricTestBase {
ServiceApi apiMock;
#Inject
SampleView fixture;
#Override
public void setUp() {
super.setUp(); //injection is performed in super
apiMock = mock(ServiceApi.class);
fixture = new SampleView(activity);
fixture.setApi(apiMock);
}
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}
}
For some reason apiMock methods are never called and verification always fails.
In my view I'm calling my api like this
apiV2.requestA()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer());
What am I missing here?
Update #1:
After some investigation it appears that when in my implementation (sample above) I observeOn(AndroidSchedulers.mainThread()) subscriber is not called. Still do not know why.
Update #2:
When subscribing just like that apiV2.requestA().subscribe(new Observer()); everything works just fine - mock api is called and test passes.
Advancing ShadowLooper.idleMainLooper(5000) did nothing. Even grabbed looper from handler in HandlerThreadScheduler and advanced it. Same result.
Update #3:
Adding actual code where API is used.
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
I'm still wrapping my head around how to properly use rxjava myself but I would try to modify your code so that you only observeOn(mainThread) on the final zipped Observable instead of doing it on both of the original request response's Observable. I would then verify if this affect the fact that you have to advance both Loopers or not.
To simply your tests and remove the need for Looper idling I would take the threading out of the equation since you don't need background processing when running tests. You can do that by having your Schedulers injected instead of creating them statically. When running your production code you'd have the AndroidSchedulers.mainThread and Schedulers.io injected and when running tests code you would inject Schedulers.immediate where applicable.
#Inject
#UIScheduler /* Inject Schedulers.immediate for tests and AndroidSchedulers.mainThread for production code */
private Scheduler mainThreadSched;
#Inject
#IOScheduler /* Inject Scheduler.immediate for tests and Schedulers.io for production code */
private Scheduler ioSched;
public void updateView(final Callback) {
Observable.zip(wrapObservable(api.requestA()), wrapObservable(api.requestB()),
new Func2<ResponseA, ResponseB, Object>() {
#Override
public Object call(ResponseA responseA, ResponseB responseB) {
return mergeBothResponses(responseA, responseB);
}
}
).observeOn(mainThreadSched)
.subscribe(new EndlessObserver<Object>() {
#Override
public void onError(Throwable e) {
Log.e(e);
listener.onError(e);
}
#Override
public void onNext(Object config) {
Log.d("Configuration updated [%s]", config.toString());
listener.onSuccess(config);
}
});
}
protected <T> Observable<T> wrapObservable(Observable<T> observable) {
return observable.subscribeOn(ioSched);
}
what version of rxjava are you using? I know there was some changes in the 0.18.* version regarding the ExecutorScheduler. I had a similar issue as you when using 0.18.3 where I wouldn't get the onComplete message because my subscription would be unsubscribe ahead of time. The only reason I'm mentioning this to you is that a fix in 0.19.0 fixed my issue.
Unfortunately I can't really explain the details of what was fixed, it's beyond my understanding at this point but if it turns out to be the same cause maybe someone with more understand could explain. Here's the link of what I'm talking about https://github.com/Netflix/RxJava/issues/1219.
This isn't much of an answer but more a heads up in case it could help you.
As #champ016 stated there were issues with RxJava versions that are lower than 0.19.0.
When using 0.19.0 the following approach works. Although still don't quite get why I have to advance BOTH loopers.
#Test
public void testSampleViewCallback() {
when(apiMock.requestA()).thenReturn(Observable.from(new ResponseA());
when(apiMock.requestB()).thenReturn(Observable.from(new ResponseB());
AtomicReference<Object> testResult = new AtomicReference<>();
fixture.updateView(new Callback() {
#Override
public void onSuccess(Object result) {
testResult.set(result);
}
#Override
public void onError(Throwable error) {
throw new RuntimeException(error);
}
});
ShadowLooper.idleMainLooper(5000);
Robolectric.shadowOf(
Reflection.field("handler")
.ofType(Handler.class)
.in(AndroidSchedulers.mainThread())
.get().getLooper())
.idle(5000);
verify(apiMock, times(1)).requestA();
verify(apiMock, times(1)).requestB();
assertNotNull(testResult.get());
}

Categories

Resources