I'm currently implementing a UI crawler for hybrid Android apps (i.e., Android apps implemented using PhoneGap), and the crawler requires periodic restarts to generate a model of the UI. Here, by "restart", I mean uninstall the app, do a fresh install of the same app, and resume execution of the crawler. (The idea is to get to the same initial state as before. One could argue that I can just reload the initial HTML page, but this would only work if the app does not save and reuse any data like login information, etc. The data needs to be fresh - i.e., what it was when the app was installed for the first time).
I'm quite new to Android app development, so I decided to test out what is perhaps the most naive method possible. The test code I've written is shown below. The method testReinstall() runs as an Android JUnit test, and I'm using Robotium 4.3 to execute clicks and other events on the app.
package com.example.googleauthenticator.test;
import java.io.IOException;
import android.test.ActivityInstrumentationTestCase2;
import com.example.googleauthenticator.MainActivity;
import com.jayway.android.robotium.solo.By;
import com.jayway.android.robotium.solo.Solo;
public class ReinstallTest extends ActivityInstrumentationTestCase2<MainActivity> {
private Solo solo;
public ReinstallTest() {
super(MainActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
}
public void testReinstall() {
//Reinstall app
Runtime rt = Runtime.getRuntime();
Process pr;
try {
pr = rt.exec("adb uninstall com.example.googleauthenticator"); //Uninstall
pr.waitFor();
pr = rt.exec("adb install GoogleAuthenticator.apk"); //Install
pr.waitFor();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException ie) {
// TODO Auto-generated catch block
ie.printStackTrace();
}
System.out.println("Reinstall done!");
//Perform some action
if (solo.waitForWebElement(By.id("add"))) {
solo.clickOnWebElement(By.id("add"));
}
}
#Override
protected void tearDown() throws Exception{
solo.finishOpenedActivities();
super.tearDown();
}
}
I tried running the above code and it seems like it was able to successfully uninstall the app (or, at the very least, I know for a fact that the data/data/com.example.googleauthenticator folder was removed). However, the app does not get reinstalled (i.e., the data/data/com.example.googleauthenticator folder is still not there), and I presume this is related to the fact that by the time the first call to pr.waitFor() is reached, testReinstall() terminates due to a "Process crash", and the following message appears in the LogCat:
11-08 17:08:32.763: W/PluginManager(9285): Can't find plugin: com.example.googleauthenticator
What am I missing here? Is there a better/more correct way?
EDIT: To be clear, I'm also getting the following error message:
11-08 17:33:40.883: D/WebKit(14828): Unabled to create LocalStorage database path /data/data/com.example.googleauthenticator/app_database/localstorage
You cannot remove the application you are testing, infact exiting the application you are testing will mean that your test stops running this is because with instrumentation your test and your application are the same process.
Just because you cannot do what you want via robotium/instrumentation though does not mean what you want to do is impossible, you will just need to use an automation tool not based on instrumentation that runs off device, Calabash for example will automatically reinstall the application for you depending on if you use the correct annotation, this might be a better fit for what you want to do.
Related
I started to learning about Android lately.
Now i'm trying to test some random Application with Robotium with Black-box testing method with no sources code or ID.
The app i tried to test:
https://play.google.com/store/apps/details?id=com.zing.zalo&hl=en
.
Just some simple function like: Open the app, login, log out.
I implented External Jar "robotium" in the JUnit test project, build path is checked with robotium and everything else.
I struggled with Pkg name, and activity name, then i saw some adb platform-toll "adb shell dumpsys activity" in the site below to get the name from my phone.
ADB - Android - Getting the name of the current activity
And many hours i read and searched from many sites this is the code i came up with:
public class LoginZalo extends ActivityInstrumentationTestCase2 {
public static Class LauncherActivityFullClass;
public Solo solo;
String pkg = "com.zing.zalo";
private static String launcher_activity_full_class = "/.ui.ZaloLauncherActivity";//login interface
static{
try {
LauncherActivityFullClass = Class.forName(launcher_activity_full_class);
} catch (Exception e) {
throw new RuntimeException();
}
}
public LoginZalo()
{
super(LauncherActivityFullClass);
}
protected void setUp() throws Exception {
solo = new Solo(getInstrumentation(),getActivity());
}
public void ZaloLogin()
{
solo.clickOnButton("LOGIN");
//also tried solo.clickOnButton(0); or solo.clickOnButton(1); or even clickOnText("LOGIN");
}
#Override
protected void tearDown() throws Exception {
// TODO Auto-generated method stub
solo.finishOpenedActivities();
}
}
AndroidManifest add target package.
<instrumentation
android:name="android.test.InstrumenttationTestRunner"
android:targetPackage="com.zing.zalo" />
But i only got a red error "Test run failed: No test results"in the console. I saw there is an comment on Robotium GitHub said Robotium can't do Black-box testing. I tried so many things from many site for many hours but getting nowhere. So i wonder, can Robotium Eclipse can do Black-box testing ? Maybe i did something wrong, because on Github site, they said Robotium can do black-box testing. Or i have to do it with Android Studio + Robotium Recorder ? Or maybe i should try something else like Appium ?
We are trying to make a sync adapter service in Android which will run in the background when the app is killed.
This service will fetch some data from JsonStore and will sync up with the server.
Code:
try {
URI adapterPath = new URI("/dummy/adapter");
WLResourceRequest request = new WLResourceRequest(adapterPath,WLResourceRequest.POST);
request.send(new AdapterListener(new CallbackAdapter() {
#Override
public void onFetch(String response) {
// TODO Auto-generated method stub
}
#Override
public void onError(String error) {
// TODO Auto-generated method stub
}
}));
} catch (URISyntaxException e) {
e.printStackTrace();
}
Problems:
When we try to run service in a different process, we get an error at line ( WLResourceRequest request = new WLResourceRequest(adapterPath,WLResourceRequest.POST);) that WL.getInstance should be called after WL.createInstance but we can not create WL instance in service because it needs an instance of ACTIVITY.
When we try to run service in the same process, in which the app is currently running, everything works fine untile app is running but if we kill the app same things happen which are happening at point 1.
Questions:
Is there a way we can create WL instance in service.
Is there a way we can let WL instance initialized forever, even though user kills the app.
Is there a way we can let our app run forever with WL instance initialized forever.
I got it working, all you need to add
WL.App.setKeepAliveInBackground(true);
into the js file and WL instance will work with sync adapters and services in Android.
Running the MobileFirst Android SDK in an Android Service is currently not supported. There is an open feature request for this ability, so please feel free to add your vote if you can want to make this happen. Search here: https://mobilefirstplatform.ibmcloud.com/help/
Is there a way to execute a method after the App crashed (a certain number times)?
Example: Sometimes with every run data changes in a way that the app does not crash anymore. So, this way it may recover itself after trying to start it for 2 times for example. The 3rd time would run smoothly again. Just an example!
My only idea right now would be to wrap everything inside the onCreate method in my main Activity in a generic try-catch block (catching Exception) handler. I don't think this is smart for several reasons, for example performance.
To catch every uncaught exception you can use following snippet:
public class App extends Application {
public void onCreate() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
#Override
public void uncaughtException(Thread t, Throwable e) {
Log.e("TAG","Implement your recovery strategy here, e.g. clean database or cache: " + e.toString());
}
});
}
}
Please note, that this is very 'hacky' solution, and if your app is in corrupted state very often there is probably something wrong with your code.
In my Android application I utilize setDefaultUncaughtExceptionHandler to store information about unhandled exceptions locally on a user device. After some feedback I suspect that this code prevents the built-in Google's error-reporting feature from work, because I do not see error reports in the developer console, while exceptions are reported by users. Their devices are well past 2.2, where the error-reporting was introduced. Could it be that specific device with, say, 4.0.3 does not support this feature? If yes, how can I detect this programmatically?
I can't find information regarding this in Android documentation. I'd like both standard error-reporting and my custom handling work together. In my custom exception handler I call Thread.getDefaultUncaughtExceptionHandler() to get default handler, and in my implementation of uncaughtException I propagate exception to this default handler as well.
I first tried calling System.exit(1); as mentioned in this SO answer, but that didn't work.
Finally solved it by calling the uncaughtException(Thread thread, Throwable ex) again on Androids default UncaughtExceptionHandler (found it by checking the ACRA source code.
Example Activity
public class MainActivity extends Activity implements Thread.UncaughtExceptionHandler {
private Thread.UncaughtExceptionHandler _androidUncaughtExceptionHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_androidUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
// Rest onCreate
setContentView(R.layout.main_activity);
}
//#Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
// Do your stuff with the exception
} catch (Exception e) {
/* Ignore */
} finally {
// Let Android show the default error dialog
_androidUncaughtExceptionHandler.uncaughtException(thread, ex);
}
}
}
Yes, this will stop the inbuilt error report. The user is given a dialog when your app crashes, with an option to report the error via Google Play. However, if you use setDefaultUncaughtExceptionHandler() then the exception is handled within your app, and no option is given to report it.
I recommend that you integrate ACRA into your project, as it allows you to easily receive error reports upon crashes.
First thing my app does is checking for "su" since it's necessary for the app to work. Even though it sometimes work, often after typing "killall packageName" in the terminal. I've done a simple test application and I can't get it to work every time.
Code where it happens:
String[] args = new String[] { "su" };
Log.v(TAG, "run(" + Arrays.toString(args) + ")");
FutureTask<Process> task = new FutureTask<Process>(new Callable<Process>() {
#Override
public Process call() throws Exception {
return Runtime.getRuntime().exec(args);
}
});
try {
Executors.newSingleThreadExecutor().execute(task);
return task.get(10, TimeUnit.SECONDS);
} catch (Throwable t) {
task.cancel(true);
throw new IOException("failed to start process within 10 seconds", t);
}
Complete project: https://github.com/chrulri/android_testexec
Since this app does nothing more than running exec() in the first place, I cannot close any previously opened file descriptors as mentioned in another stackoverflow question: https://stackoverflow.com/a/11317150/1145705
PS: I run Android 4.0.3 / 4.0.4 on different devices.
3c71 was right about open file descriptors. In my case, it was the AdMob SDK which caused the problems since it was sometimes (re-)loading the Ads from the web at the sime time I tried to call exec(..) leaving me hanging in a deadlock.
My solution is to fork a "su" process ONCE and reuse it for all commands and load the Ads AFTER forking that process.
To use Runtime.exec safely you should wait for the process to finish and consume the output and error streams, preferably concurrently (to prevent blocking):
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html