Skip to content

Commit

Permalink
Added Google Espresso testing framework
Browse files Browse the repository at this point in the history
Migrate export tests to espresso
Added custom test runner for espresso tests
Disable animation when running test with debug builds
  • Loading branch information
codinguser committed May 19, 2015
1 parent 3bb3d90 commit 6bdbc50
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 50 deletions.
28 changes: 27 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ android {
resValue "string", "dropbox_app_secret", "h2t9fphj3nr4wkw"
resValue "string", "manifest_dropbox_app_key", "db-dhjh8ke9wf05948"
}
testInstrumentationRunner "org.gnucash.android.test.ui.GnucashAndroidTestRunner"

}

packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}

applicationVariants.all { variant ->
Expand Down Expand Up @@ -114,6 +125,18 @@ android {
}
}


task grantAnimationPermission(type: Exec, dependsOn: ['installDevelopmentDebug', 'installBetaDebug', 'installProductionDebug']) { // or install{productFlavour}{buildType}
// commandLine "adb shell pm grant $android.defaultConfig.packageName android.permission.SET_ANIMATION_SCALE".split(' ')
commandLine "adb shell pm grant $android.defaultConfig.applicationId android.permission.SET_ANIMATION_SCALE".split(' ')
}
tasks.whenTaskAdded { task ->
if (task.name.startsWith('connectedAndroidTest')) {
task.dependsOn grantAnimationPermission
}
}


dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile('com.android.support:support-v4:22.1.1',
Expand All @@ -132,8 +155,11 @@ dependencies {
'org.assertj:assertj-core:1.7.1'
)
androidTestCompile('com.jayway.android.robotium:robotium-solo:5.3.1')

androidTestCompile 'com.android.support.test:runner:0.2'
androidTestCompile 'com.android.support.test:rules:0.2'
androidTestCompile('com.squareup.assertj:assertj-android:1.0.0'){
exclude group: 'com.android.support', module:'support-annotations'
}
androidTestCompile ('com.android.support.test.espresso:espresso-core:2.1')
androidTestCompile 'com.android.support:support-annotations:22.1.1'
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012 Ngewi Fet <ngewif@gmail.com>
* Copyright (c) 2012 - 2015 Ngewi Fet <ngewif@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012 Ngewi Fet <ngewif@gmail.com>
* Copyright (c) 2012 - 2015 Ngewi Fet <ngewif@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,10 +19,11 @@
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.preference.PreferenceManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;

import com.robotium.solo.Solo;
import android.widget.CompoundButton;

import org.gnucash.android.R;
import org.gnucash.android.db.AccountsDbAdapter;
Expand All @@ -39,31 +40,48 @@
import org.gnucash.android.model.Split;
import org.gnucash.android.model.Transaction;
import org.gnucash.android.ui.account.AccountsActivity;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.File;
import java.util.Currency;
import java.util.List;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.allOf;

@RunWith(AndroidJUnit4.class)
public class ExportTransactionsTest extends
ActivityInstrumentationTestCase2<AccountsActivity> {

private Solo mSolo;
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
private AccountsDbAdapter mAccountsDbAdapter;
private TransactionsDbAdapter mTransactionsDbAdapter;
private SplitsDbAdapter mSplitsDbAdapter;

private AccountsActivity mAcccountsActivity;

public ExportTransactionsTest() {
super(AccountsActivity.class);
}

@Override
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
super.setUp();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mAcccountsActivity = getActivity();
AccountsActivityTest.preventFirstRunDialogs(getInstrumentation().getTargetContext());
mSolo = new Solo(getInstrumentation(), getActivity());

mDbHelper = new DatabaseHelper(getActivity());
try {
Expand All @@ -75,18 +93,20 @@ protected void setUp() throws Exception {
mSplitsDbAdapter = new SplitsDbAdapter(mDb);
mTransactionsDbAdapter = new TransactionsDbAdapter(mDb, mSplitsDbAdapter);
mAccountsDbAdapter = new AccountsDbAdapter(mDb, mTransactionsDbAdapter);
mAccountsDbAdapter.deleteAllRecords();

Account account = new Account("Exportable");
Transaction transaction = new Transaction("Pizza");
transaction.setNote("What up?");
transaction.setTime(System.currentTimeMillis());
Split split = new Split(new Money("8.99", "USD"), account.getUID());
split.setMemo("Hawaii is the best!");
transaction.addSplit(split);
transaction.addSplit(split);
transaction.addSplit(split.createPair(mAccountsDbAdapter.getOrCreateImbalanceAccountUID(Currency.getInstance("USD"))));
account.addTransaction(transaction);

mAccountsDbAdapter.addAccount(account);

}

/**
Expand All @@ -95,17 +115,20 @@ protected void setUp() throws Exception {
* If this test fails, it may be due to the file being created and tested in different minutes of the clock
* Just try rerunning it again.
*/
@Test
public void testOfxExport(){
testExport(ExportFormat.OFX);
}

/**
* Test the export of transactions in the QIF format
*/
@Test
public void testQifExport(){
testExport(ExportFormat.QIF);
}

@Test
public void testXmlExport(){
testExport(ExportFormat.XML);
}
Expand All @@ -117,86 +140,70 @@ public void testXmlExport(){
public void testExport(ExportFormat format){
File folder = new File(Exporter.EXPORT_FOLDER_PATH);
folder.mkdirs();
mSolo.sleep(5000);
assertThat(folder).exists();

for (File file : folder.listFiles()) {
file.delete();
}
//legacy menu will be removed in the future
//onView(withId(R.id.menu_export)).perform(click());
onView(withId(android.R.id.home)).perform(click());
onView(withText(R.string.nav_menu_export)).perform(click());
onView(withText(format.name())).perform(click());

mSolo.clickOnActionBarItem(R.id.menu_export);
mSolo.waitForDialogToOpen(5000);

mSolo.waitForText(getActivity().getString(R.string.title_export_dialog));

mSolo.clickOnText(format.name());
mSolo.clickOnView(mSolo.getView(R.id.btn_save));

mSolo.waitForDialogToClose(10000);
mSolo.sleep(5000); //sleep so that emulators can save the file
onView(withId(R.id.btn_save)).perform(click());

assertThat(folder.listFiles().length).isEqualTo(1);
File exportFile = folder.listFiles()[0];
assertThat(exportFile.getName()).endsWith(format.getExtension());
}

@Test
public void testDeleteTransactionsAfterExport(){
assertThat(mTransactionsDbAdapter.getAllTransactionsCount()).isGreaterThan(0);

PreferenceManager.getDefaultSharedPreferences(getActivity()).edit()
.putBoolean(mSolo.getString(R.string.key_delete_transactions_after_export), true).commit();
.putBoolean(mAcccountsActivity.getString(R.string.key_delete_transactions_after_export), true).commit();

testExport(ExportFormat.QIF);

assertThat(mTransactionsDbAdapter.getAllTransactionsCount()).isEqualTo(0);
PreferenceManager.getDefaultSharedPreferences(getActivity()).edit()
.putBoolean(mSolo.getString(R.string.key_delete_transactions_after_export), false).commit();
.putBoolean(mAcccountsActivity.getString(R.string.key_delete_transactions_after_export), false).commit();
}

/**
* Test creating a scheduled export
* Does not work on Travis yet
*/
public void atestCreateExportSchedule(){
// mSolo.setNavigationDrawer(Solo.OPENED);
// mSolo.clickOnText(mSolo.getString(R.string.nav_menu_export));
mSolo.clickOnActionBarItem(R.id.menu_export);
mSolo.waitForDialogToOpen(5000);

mSolo.clickOnText(ExportFormat.XML.name());
mSolo.clickOnView(mSolo.getView(R.id.input_recurrence));
mSolo.waitForDialogToOpen();
mSolo.sleep(3000);
mSolo.clickOnButton(0); //switch on the recurrence dialog
mSolo.sleep(2000);
mSolo.pressSpinnerItem(0, -1);
mSolo.sleep(2000);
mSolo.clickOnButton(1);
mSolo.sleep(3000);
mSolo.clickOnButton(5); //the export button is the second
mSolo.waitForDialogToClose();

mSolo.sleep(5000); //wait for database save
@Test
public void shouldCreateExportSchedule(){
onView(withId(android.R.id.home)).perform(click());
onView(withText(R.string.nav_menu_export)).perform(click());

onView(withText(ExportFormat.XML.name())).perform(click());
onView(withId(R.id.input_recurrence)).perform(click());

//switch on recurrence dialog
onView(allOf(isAssignableFrom(CompoundButton.class), isDisplayed(), isEnabled())).perform(click());
onView(withText("Done")).perform(click());

onView(withId(R.id.btn_save)).perform(click());
ScheduledActionDbAdapter scheduledactionDbAdapter = new ScheduledActionDbAdapter(mDb);
List<ScheduledAction> scheduledActions = scheduledactionDbAdapter.getAllEnabledScheduledActions();
assertThat(scheduledActions)
.hasSize(1)
.extracting("mActionType").contains(ScheduledAction.ActionType.BACKUP);

ScheduledAction action = scheduledActions.get(0);
assertThat(action.getPeriodType()).isEqualTo(PeriodType.DAY);
assertThat(action.getPeriodType()).isEqualTo(PeriodType.WEEK);
assertThat(action.getEndTime()).isEqualTo(0);
}

//todo: add testing of export flag to unit test
//todo: add test of ignore exported transactions to unit tests
@Override
protected void tearDown() throws Exception {
mSolo.finishOpenedActivities();
mSolo.waitForEmptyActivityStack(20000);
mSolo.sleep(5000);
mAccountsDbAdapter.deleteAllRecords();
@After public void tearDown() throws Exception {
mDbHelper.close();
mDb.close();
super.tearDown();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2015 Ngewi Fet <ngewif@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.gnucash.android.test.ui;

import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.support.test.runner.AndroidJUnitRunner;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* Custom test runner
*/
public class GnucashAndroidTestRunner extends AndroidJUnitRunner{
private static final String TAG = "Primer";
private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
private static final float DISABLED = 0.0f;
private static final float DEFAULT = 1.0f;

@Override
public void onCreate(Bundle args) {
super.onCreate(args);
// as time goes on we may actually need to process our arguments.
disableAnimation();

}

@Override
public void onDestroy() {
enableAnimation();
super.onDestroy();
}

private void disableAnimation() {
int permStatus = getContext().checkCallingOrSelfPermission(ANIMATION_PERMISSION);
if (permStatus == PackageManager.PERMISSION_GRANTED) {
if (reflectivelyDisableAnimation(DISABLED)) {
Log.i(TAG, "All animations disabled.");
} else {
Log.i(TAG, "Could not disable animations.");
}
} else {
Log.i(TAG, "Cannot disable animations due to lack of permission.");
}
}

private void enableAnimation(){
int permStatus = getContext().checkCallingOrSelfPermission(ANIMATION_PERMISSION);
if (permStatus == PackageManager.PERMISSION_GRANTED) {
if (reflectivelyDisableAnimation(DEFAULT)) {
Log.i(TAG, "All animations disabled.");
} else {
Log.i(TAG, "Could not disable animations.");
}
} else {
Log.i(TAG, "Cannot disable animations due to lack of permission.");
}
}

private boolean reflectivelyDisableAnimation(float animationScale) {
try {
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales",
float[].class);
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");

IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
for (int i = 0; i < currentScales.length; i++) {
currentScales[i] = animationScale;
}
setAnimationScales.invoke(windowManagerObj, currentScales);
return true;
} catch (ClassNotFoundException cnfe) {
Log.w(TAG, "Cannot disable animations reflectively.", cnfe);
} catch (NoSuchMethodException mnfe) {
Log.w(TAG, "Cannot disable animations reflectively.", mnfe);
} catch (SecurityException se) {
Log.w(TAG, "Cannot disable animations reflectively.", se);
} catch (InvocationTargetException ite) {
Log.w(TAG, "Cannot disable animations reflectively.", ite);
} catch (IllegalAccessException iae) {
Log.w(TAG, "Cannot disable animations reflectively.", iae);
} catch (RuntimeException re) {
Log.w(TAG, "Cannot disable animations reflectively.", re);
}
return false;
}
}
Loading

0 comments on commit 6bdbc50

Please sign in to comment.