Skip to content

Commit

Permalink
WIP eventto
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 650421011
  • Loading branch information
brettchabot authored and copybara-androidxtest committed Jul 9, 2024
1 parent 59a679d commit 41506ff
Show file tree
Hide file tree
Showing 17 changed files with 550 additions and 1 deletion.
1 change: 1 addition & 0 deletions espresso/core/java/androidx/test/espresso/base/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ android_library(
"ViewHierarchyExceptionHandler.java",
],
),
visibility = ["//visibility:public"],
deps = [
":default_failure_handler",
":idling_resource_registry",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public final class ViewFinderImpl implements ViewFinder {
private final Provider<View> rootViewProvider;

@Inject
ViewFinderImpl(Matcher<View> viewMatcher, Provider<View> rootViewProvider) {
public ViewFinderImpl(Matcher<View> viewMatcher, Provider<View> rootViewProvider) {
this.viewMatcher = viewMatcher;
this.rootViewProvider = rootViewProvider;
}
Expand Down
6 changes: 6 additions & 0 deletions eventto/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
alias(
name = "eventto",
testonly = 1,
actual = "//eventto/java/androidx/test/eventto",
visibility = ["//visibility:public"],
)
27 changes: 27 additions & 0 deletions eventto/java/androidx/test/eventto/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("@build_bazel_rules_android//android:rules.bzl", "android_library")

android_library(
name = "eventto",
testonly = 1,
srcs = glob(["*.java"]),
visibility = [
"//eventto:__pkg__",
],
deps = [
"//core",
"//espresso/core/java/androidx/test/espresso:espresso_compiletime",
"//espresso/core/java/androidx/test/espresso:framework",
"//espresso/core/java/androidx/test/espresso:interface",
"//espresso/core/java/androidx/test/espresso/base",
"//espresso/core/java/androidx/test/espresso/matcher",
"//espresso/core/java/androidx/test/espresso/util",
"//opensource/androidx:annotation",
"//runner/monitor",
"@maven//:com_google_code_findbugs_jsr305",
"@maven//:com_google_guava_guava",
"@maven//:javax_inject_javax_inject",
"@maven//:junit_junit",
"@maven//:org_hamcrest_hamcrest_core",
"@maven_listenablefuture//:com_google_guava_listenablefuture",
],
)
21 changes: 21 additions & 0 deletions eventto/java/androidx/test/eventto/Eventto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package androidx.test.eventto;

import android.view.View;
import androidx.annotation.CheckResult;
import java.time.Duration;
import javax.annotation.CheckReturnValue;
import org.hamcrest.Matcher;

public class Eventto {
private Eventto() {}

@CheckReturnValue
@CheckResult
public static EventtoViewInteraction onView(Matcher<View> matcher) {
return new EventtoViewInteraction(matcher);
}

public static void setDefaultTimeout(Duration duration) {
EventtoViewInteraction.setDefaultTimeout(duration);
}
}
32 changes: 32 additions & 0 deletions eventto/java/androidx/test/eventto/EventtoViewAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package androidx.test.eventto;

import android.view.View;
import org.hamcrest.Matcher;

public interface EventtoViewAction {

/**
* A mechanism for ViewActions to specify what type of views they can operate on.
*
* <p>A ViewAction can demand that the view passed to perform meets certain constraints. For
* example it may want to ensure the view is already in the viewable physical screen of the device
* or is of a certain type.
*
* @return a <a href="http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html">
* <code>Matcher</code></a> that will be tested prior to calling perform.
*/
public Matcher<View> getConstraints();

/**
* Returns a description of the view action. The description should not be overly long and should
* fit nicely in a sentence like: "performing %description% action on view with id ..."
*/
public String getDescription();

/**
* Performs this action on the given view.
*
* @param view the view to act upon. never null.
*/
public void perform(View view);
}
164 changes: 164 additions & 0 deletions eventto/java/androidx/test/eventto/EventtoViewInteraction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package androidx.test.eventto;


import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.ViewFinder;
import androidx.test.espresso.base.ViewFinderImpl;
import androidx.test.internal.util.Checks;
import androidx.test.platform.view.inspector.WindowInspectorCompat;
import androidx.test.platform.view.inspector.WindowInspectorCompat.ViewRetrievalException;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Provider;
import org.hamcrest.Matcher;

public class EventtoViewInteraction {
// private final Matcher<View> viewMatcher;
private final ViewFinder viewFinder;
private long timeoutMillis;
private final Handler mainHandler = new Handler(Looper.getMainLooper());

private static long defaultTimeoutMillis = Duration.ofSeconds(5).toMillis();

EventtoViewInteraction(Matcher<View> viewMatcher) {
// this.viewMatcher = viewMatcher;
this.viewFinder = new ViewFinderImpl(viewMatcher, new SimpleRootViewPicker());
this.timeoutMillis = defaultTimeoutMillis;
}

static void setDefaultTimeout(Duration duration) {
defaultTimeoutMillis = duration.toMillis();
}

public EventtoViewInteraction withTimeout(Duration timeout) {
timeoutMillis = timeout.toMillis();
return this;
}

public void check(ViewAssertion assertion) {
ViewAssertionCallable viewAssertionCallable = new ViewAssertionCallable(viewFinder, assertion);
performRetryingTaskOnUiThread(viewAssertionCallable);
}

private void performRetryingTaskOnUiThread(Callable<ExecutionStatus> viewInteractionCallable) {
long startTime = SystemClock.uptimeMillis();
long remainingTimeout = timeoutMillis;
long pollDelay = 0;
while (remainingTimeout > 0) {

FutureTask<ExecutionStatus> uiTask = new FutureTask<>(viewInteractionCallable);
mainHandler.postDelayed(uiTask, pollDelay);
try {
if (uiTask.get(remainingTimeout, TimeUnit.MILLISECONDS) == ExecutionStatus.SUCCESS) {
return;
}
remainingTimeout = SystemClock.uptimeMillis() - startTime - timeoutMillis;
pollDelay = 100;
} catch (InterruptedException ie) {
throw new RuntimeException("Interrupted running UI task", ie);
} catch (ExecutionException ee) {
Throwable cause = ee.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException(cause);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
}

public void perform(ViewAction action) {
ViewActionCallable callable =
new ViewActionCallable(viewFinder, action, new EventtoUiController());
performRetryingTaskOnUiThread(callable);
}

private static class SimpleRootViewPicker implements Provider<View> {

@Override
public View get() {

try {
List<View> views = WindowInspectorCompat.getGlobalWindowViews();
// TODO: handle more than 1 view
Checks.checkArgument(views.size() <= 1);
if (views.size() == 1) {
return views.get(0);
}
} catch (ViewRetrievalException e) {
//
}
return null;
}
}

private enum ExecutionStatus {
SUCCESS,
RESCHEDULE
}

private static class ViewAssertionCallable implements Callable<ExecutionStatus> {
private final ViewFinder viewFinder;
private final ViewAssertion viewAssertion;

ViewAssertionCallable(ViewFinder viewFinder, ViewAssertion viewAssertion) {
this.viewFinder = viewFinder;
this.viewAssertion = viewAssertion;
}

@Override
public ExecutionStatus call() throws Exception {
Log.i("Eventto", "Running ViewAssertionCallable");
try {
View matchedView = viewFinder.getView();
viewAssertion.check(matchedView, null);
return ExecutionStatus.SUCCESS;
} catch (NoMatchingViewException e) {
Log.i("Eventto", "Could not find view, retrying", e);
return ExecutionStatus.RESCHEDULE;
}
}
}

private static class ViewActionCallable implements Callable<ExecutionStatus> {
private final ViewFinder viewFinder;
private final ViewAction viewAction;
private final UiController uiController;

ViewActionCallable(
ViewFinder viewFinder, EventoViewAction viewAction, UiController uiController) {
this.viewFinder = viewFinder;
this.viewAction = viewAction;
this.uiController = uiController;
}

@Override
public ExecutionStatus call() throws Exception {
Log.i("Eventto", "Running ViewAssertionCallable");
try {
View matchedView = viewFinder.getView();
viewAction.perform(uiController, matchedView);
return ExecutionStatus.SUCCESS;
} catch (NoMatchingViewException e) {
Log.i("Eventto", "Could not find view, retrying", e);
return ExecutionStatus.RESCHEDULE;
}
}
}
}
35 changes: 35 additions & 0 deletions eventto/javatests/androidx/test/eventto/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@build_bazel_rules_android//android:rules.bzl", "android_local_test")
load("//build_extensions:android_library_test.bzl", "axt_android_library_test")
load("//build_extensions:phone_devices.bzl", "devices")

java_library(
name = "robolectric_config",
resources = ["robolectric.properties"],
)

android_local_test(
name = "EventtoTest",
srcs = ["EventtoTest.java"],
jvm_flags = ["-Drobolectric.looperMode=INSTRUMENTATION_TEST"],
deps = [
":robolectric_config",
"//espresso/core/java/androidx/test/espresso",
"//eventto",
"//eventto/javatests/androidx/test/eventto/fixtures",
"//ext/junit",
"@maven//:junit_junit",
],
)

axt_android_library_test(
name = "EventtoTest_android",
srcs = ["EventtoTest.java"],
device_list = devices([34]),
deps = [
"//espresso/core/java/androidx/test/espresso",
"//eventto",
"//eventto/javatests/androidx/test/eventto/fixtures",
"//ext/junit",
"@maven//:junit_junit",
],
)
49 changes: 49 additions & 0 deletions eventto/javatests/androidx/test/eventto/EventtoTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package androidx.test.eventto;

import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import androidx.test.eventto.fixtures.SimpleActivity;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class EventtoTest {

@Rule
public ActivityScenarioRule<SimpleActivity> activityScenarioRule =
new ActivityScenarioRule<>(SimpleActivity.class);

// @Test
// public void eventto_find() throws Throwable {
// Eventto.onView(withText("Text View")).check(matches(isDisplayed()));
// }

@Test
public void eventto_click() throws Throwable {
Eventto.onView(withText("Launch delayed")).perform(click());
}

//
// @Test
// public void espresso_click() throws Throwable {
// onView(withText("Launch delayed")).perform(click());
//
// // expect another activity launch
// onView(withText("Delayed Activity")).check(matches(isDisplayed()));
// }

// @Test
// public void eventto_findFails() throws Throwable {
// onViewWithText("Text View not there").assertThat(isDisplayed());
// }

// @Test
// public void espressoFind() {
// onView(withText("Text View")).check(matches(isDisplayed()));
// }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Manifest for ATSL integration tests
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >

<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />

<application>
<activity android:name="androidx.test.eventto.fixtures.SimpleActivity"
android:label="Activity Label"
android:exported="true" >
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="androidx.test.eventto.fixtures.DelayedActivity"
android:exported="true" >
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
14 changes: 14 additions & 0 deletions eventto/javatests/androidx/test/eventto/fixtures/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
load("@build_bazel_rules_android//android:rules.bzl", "android_library")

android_library(
name = "fixtures",
srcs = glob(["*.java"]),
exports_manifest = 1,
manifest = "AndroidManifest.xml",
resource_files = glob(["res/**"]),
visibility = [
"//eventto/javatests/androidx/test/eventto:__subpackages__",
],
deps = [
],
)
Loading

0 comments on commit 41506ff

Please sign in to comment.