Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outreachy2024/rebase clean fix broken UI tests #288

Open
wants to merge 16 commits into
base: upgrade-jdk11
Choose a base branch
from
Open
40 changes: 34 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ version: 2.1

orbs:
android: circleci/[email protected]

jobs:
test:
description: Runs unit tests and instrumented tests on the Android Common
Expand All @@ -17,50 +18,77 @@ jobs:
command: sudo chmod +x gradlew

- android/create-avd:
avd-name: avd
avd-name: Pixel_7_Pro
system-image: system-images;android-30;google_apis;x86
install: true
system-image: system-images;android-29;default;x86

- run:
name: Start ADB server
command: adb start-server

- android/start-emulator:
avd-name: avd
avd-name: Pixel_7_Pro
no-window: true
restore-gradle-cache-prefix: v1
memory: 4096
post-emulator-launch-assemble-command: ./gradlew installSnapshotDebug
memory: 4096
post-emulator-launch-assemble-command: ./gradlew installSnapshotDebug

- android/disable-animations

- run:
name: Wait for Emulator to be Ready
command: adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; sleep 5'

- run:
name: Restart ADB server
command: adb kill-server && adb start-server

- android/run-tests:
test-command: ./gradlew testSnapshotDebugUnitTest

- android/run-tests:
test-command: ./gradlew grantPermissionForODKXApp connectedSnapshotDebugAndroidTest
test-command: ./gradlew grantPermissionForODKXApp connectedSnapshotDebugAndroidTest --info

- android/save-gradle-cache:
cache-prefix: v1

- store_artifacts:
name: Store Test Results
path: services_app/build/outputs/androidTest-results

- store_artifacts:
name: Store Test Reports
path: services_app/build/reports

build:
docker:
- image: cimg/android:2024.01

steps:
- checkout

- run:
name: Chmod Permissions
command: sudo chmod +x gradlew

- android/restore-gradle-cache:
cache-prefix: v1

- run:
name: Download Dependencies
command: ./gradlew androidDependencies

- android/save-gradle-cache:
cache-prefix: v1

- run:
name: Build Services
command: ./gradlew assembleSnapshotDebug

- store_artifacts:
name: Store Build Artifacts
path: services_app/build/outputs/apk

- persist_to_workspace:
root: .
paths:
Expand Down
119 changes: 76 additions & 43 deletions services_app/src/androidTest/java/org/opendatakit/BaseUITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.google.android.gms.common.internal.Preconditions.checkNotNull;
Expand All @@ -16,39 +14,21 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Checkable;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.preference.CheckBoxPreference;
import android.view.View;
import android.widget.Checkable;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.core.app.ActivityScenario;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.action.GeneralClickAction;
import androidx.test.espresso.action.Press;
import androidx.test.espresso.action.Tap;
import androidx.test.espresso.action.ViewActions;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.intent.Intents;
import androidx.test.espresso.matcher.BoundedMatcher;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Rule;
import org.opendatakit.services.R;
import androidx.test.espresso.intent.Intents;
import androidx.test.espresso.matcher.BoundedMatcher;
import androidx.test.espresso.util.HumanReadables;
import androidx.test.espresso.util.TreeIterables;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.GrantPermissionRule;

Expand All @@ -57,14 +37,18 @@
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.opendatakit.properties.CommonToolProperties;
import org.opendatakit.properties.PropertiesSingleton;
import org.opendatakit.services.R;
import org.opendatakit.utilities.LocalizationUtils;
import org.opendatakit.utilities.ODKFileUtils;

import java.io.File;
import java.util.concurrent.TimeoutException;

public abstract class BaseUITest<T extends Activity> {
private static boolean isInitialized = false;
protected final static String APP_NAME = "testAppName";
protected final static String TEST_SERVER_URL = "https://testUrl.com";
protected final static String TEST_PASSWORD = "testPassword";
Expand All @@ -78,34 +62,46 @@ public abstract class BaseUITest<T extends Activity> {
protected ActivityScenario<T> activityScenario;

@Rule
public GrantPermissionRule writeRuntimePermissionRule = GrantPermissionRule .grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);
public GrantPermissionRule writeRuntimePermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE);

@Rule
public GrantPermissionRule readtimePermissionRule = GrantPermissionRule .grant(Manifest.permission.READ_EXTERNAL_STORAGE);
public GrantPermissionRule readtimePermissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE);

@Before
public void setUp() {
Intents.init();
if (!isInitialized) {
System.out.println("Intents.init() called");
Intents.init();
isInitialized = true;
}

activityScenario = ActivityScenario.launch(getLaunchIntent());
setUpPostLaunch();
}

protected abstract void setUpPostLaunch();
protected abstract Intent getLaunchIntent();

@After
public void tearDown() throws Exception {
if (activityScenario != null) activityScenario.close();
Intents.release();
if (activityScenario != null) {
activityScenario.close();
activityScenario = null;
}

if (isInitialized) {
System.out.println("Intents.release() called");
Intents.release();
isInitialized = false;
}
}


protected abstract void setUpPostLaunch();
protected abstract Intent getLaunchIntent();
protected Context getContext() {
return InstrumentationRegistry.getInstrumentation().getTargetContext();
}

public void resetConfiguration() {
PropertiesSingleton mProps = CommonToolProperties.get(getContext()
, APP_NAME);
PropertiesSingleton mProps = CommonToolProperties.get(getContext(), APP_NAME);
mProps.clearSettings();
LocalizationUtils.clearTranslations();
File f = new File(ODKFileUtils.getTablesInitializationCompleteMarkerFile(APP_NAME));
Expand Down Expand Up @@ -172,23 +168,62 @@ protected boolean matchesSafely(final RecyclerView view) {
}
};
}

public static ViewAction waitFor(long delay) {
return new ViewAction() {
@Override public Matcher<View> getConstraints() {
return ViewMatchers.isRoot();
@Override
public Matcher<View> getConstraints() {
return isRoot();
}

@Override public String getDescription() {
return "wait for " + delay + "milliseconds";
@Override
public String getDescription() {
return "wait for " + delay + " milliseconds";
}

@Override public void perform(UiController uiController, View view) {
@Override
public void perform(UiController uiController, View view) {
uiController.loopMainThreadForAtLeast(delay);
}
};
}

public static void enableAdminMode() {
public static ViewAction waitForView(final Matcher<View> viewMatcher, final long millis) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}

@Override
public String getDescription() {
return "Wait for a specific view with id <" + viewMatcher + "> during " + millis + " millis.";
}

@Override
public void perform(final UiController uiController, final View view) {
final long startTime = System.currentTimeMillis();
final long endTime = startTime + millis;

do {
for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child)) {
return;
}
}

uiController.loopMainThreadForAtLeast(50);
} while (System.currentTimeMillis() < endTime);

throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new TimeoutException())
.build();
}
};
}
public static void enableAdminMode() {
onView(withId(androidx.preference.R.id.recycler_view))
.perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.user_restrictions)),
click()));
Expand All @@ -202,9 +237,7 @@ public static void enableAdminMode() {

protected Activity getActivity() {
final Activity[] activity1 = new Activity[1];
activityScenario.onActivity(activity -> activity1[0] =activity);
activityScenario.onActivity(activity -> activity1[0] = activity);
return activity1[0];
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
*/
public class TestConsts {
public static final String APPNAME = "unittestTMP";

public static final long WAIT_TIME = 2000;

}
Loading