public final class

AtomAction<E>

extends java.lang.Object

implements ViewAction, Bindable

 java.lang.Object

↳androidx.test.espresso.web.action.AtomAction<E>

Gradle dependencies

compile group: 'androidx.test.espresso', name: 'espresso-web', version: '3.6.1'

  • groupId: androidx.test.espresso
  • artifactId: espresso-web
  • version: 3.6.1

Artifact androidx.test.espresso:espresso-web:3.6.1 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test.espresso:espresso-web com.android.support.test.espresso:espresso-web

Androidx class mapping:

androidx.test.espresso.web.action.AtomAction android.support.test.espresso.web.action.AtomAction

Overview

A ViewAction which causes the provided Atom to be evaluated within a webview.

It is not recommended to use AtomAction directly.

Instead Web for examples of how to interact with a WebView's content through Atoms.

If you must use AtomAction directly, take care to remember that they are Stateful (unlike most ViewActions) and the caller must call AtomAction.get() to ensure that the action has completed.

Summary

Constructors
publicAtomAction(Atom<java.lang.Object> atom, WindowReference window, ElementReference element)

Creates an AtomAction.

Methods
public java.lang.Objectget()

Blocks until the atom has completed execution.

public java.lang.Objectget(long val, java.util.concurrent.TimeUnit unit)

Blocks until the atom has completed execution with a configurable timeout.

public <any>getConstraints()

public java.lang.StringgetDescription()

public java.util.concurrent.Future<java.lang.Object>getFuture()

Return a Future, which will be set and transformed from futureEval.

public IBindergetIBinder()

public java.lang.StringgetId()

public voidperform(UiController uiController, View view)

public voidsetIBinder(IBinder binder)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public AtomAction(Atom<java.lang.Object> atom, WindowReference window, ElementReference element)

Creates an AtomAction.

Parameters:

atom: the atom to execute
window: (optional/nullable) the window context to execute on.
element: (optional/nullable) the element to execute on.

Methods

public <any> getConstraints()

public java.lang.String getDescription()

public void perform(UiController uiController, View view)

public java.util.concurrent.Future<java.lang.Object> getFuture()

Return a Future, which will be set and transformed from futureEval. Espresso's public API cannot have Guava types in its method signatures, so return Future instead of ListenableFuture or SettableFuture.

public java.lang.Object get()

Blocks until the atom has completed execution.

public java.lang.Object get(long val, java.util.concurrent.TimeUnit unit)

Blocks until the atom has completed execution with a configurable timeout.

public java.lang.String getId()

public IBinder getIBinder()

public void setIBinder(IBinder binder)

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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 androidx.test.espresso.web.action;

import static androidx.test.espresso.matcher.ViewMatchers.isJavascriptEnabled;
import static androidx.test.espresso.web.util.concurrent.Futures.transform;
import static androidx.test.internal.util.Checks.checkNotNull;
import static androidx.test.internal.util.Checks.checkState;

import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import androidx.annotation.Nullable;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.remote.Bindable;
import androidx.test.espresso.web.model.Atom;
import androidx.test.espresso.web.model.ElementReference;
import androidx.test.espresso.web.model.Evaluation;
import androidx.test.espresso.web.model.WindowReference;
import androidx.test.platform.concurrent.DirectExecutor;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import kotlin.jvm.functions.Function1;
import org.hamcrest.Matcher;

/**
 * A ViewAction which causes the provided Atom to be evaluated within a webview.
 *
 * <p>It is not recommended to use AtomAction directly.
 *
 * <p>Instead {@see androidx.test.espresso.web.sugar.Web} for examples of how to interact
 * with a WebView's content through Atoms.
 *
 * <p>If you must use AtomAction directly, take care to remember that they are Stateful (unlike most
 * ViewActions) and the caller must call {@link #get()} to ensure that the action has completed.
 *
 * @param <E> The type the specific Atom returns.
 */
public final class AtomAction<E> implements ViewAction, Bindable {
  private static final String TAG = "AtomAction";
  private static final String ID = TAG;
  private static final String EVALUATION_ERROR_KEY = "evaluation_error_key";
  private final ResolvableFuture<Evaluation> futureEval = ResolvableFuture.create();
  final Atom<E> atom;
  @Nullable final WindowReference window;
  @Nullable final ElementReference element;

  private IAtomActionResultPropagator atomActionResultPropagator =
      new IAtomActionResultPropagator.Stub() {

        @Override
        public void setResult(Evaluation evaluation) throws RemoteException {
          futureEval.set(evaluation);
        }

        @Override
        public void setError(Bundle bundle) throws RemoteException {
          Throwable evalError = (Throwable) bundle.getSerializable(EVALUATION_ERROR_KEY);
          futureEval.setException(evalError);
        }
      };

  /**
   * Creates an AtomAction.
   *
   * @param atom the atom to execute
   * @param window (optional/nullable) the window context to execute on.
   * @param element (optional/nullable) the element to execute on.
   */
  public AtomAction(
      Atom<E> atom, @Nullable WindowReference window, @Nullable ElementReference element) {
    this.atom = checkNotNull(atom);
    this.window = window;
    this.element = element;
  }

  @Override
  public Matcher<View> getConstraints() {
    return isJavascriptEnabled();
  }

  @Override
  public String getDescription() {
    return String.format("Evaluate Atom: %s in window: %s with element: %s", atom, window, element);
  }

  @Override
  public void perform(UiController uiController, View view) {
    WebView webView = (WebView) view;
    if (Build.VERSION.SDK_INT >= 23 && !webView.isHardwareAccelerated()) {
      throw new PerformException.Builder()
          .withViewDescription(webView.toString())
          .withCause(
              new RuntimeException("Hardware acceleration is not supported on current device"))
          .build();
    }
    List<Object> arguments = checkNotNull(atom.getArguments(element));
    String script = checkNotNull(atom.getScript());
    final ListenableFuture<Evaluation> localEval =
        JavascriptEvaluation.evaluate(webView, script, arguments, window);
    if (null != window && Build.VERSION.SDK_INT == 19) {
      Log.w(
          TAG,
          "WARNING: KitKat does not report when an iframe is loading new content. "
              + "If you are interacting with content within an iframe and that content is changing "
              + "(eg: you have just pressed a submit button). Espresso will not be able to block "
              + "you until the new content has loaded (which it can do on all other API levels). "
              + "You will need to have some custom polling / synchronization with the iframe in "
              + "that case.");
    }

    localEval.addListener(
        new Runnable() {
          @Override
          public void run() {
            try {
              atomActionResultPropagator.setResult(localEval.get());
            } catch (ExecutionException ee) {
              reportException(ee.getCause());
            } catch (InterruptedException ie) {
              reportException(ie);
            } catch (RemoteException re) {
              reportException(re);
            }
          }
        },
        DirectExecutor.INSTANCE);
  }

  private void reportException(Throwable throwable) {
    Bundle errorBundle = new Bundle();
    errorBundle.putSerializable(EVALUATION_ERROR_KEY, throwable);
    try {
      atomActionResultPropagator.setError(errorBundle);
    } catch (RemoteException re) {
      Log.e(TAG, "Cannot report error to result propagator", re);
    }
  }

  /**
   * Return a Future, which will be set and transformed from futureEval. Espresso's public API
   * cannot have Guava types in its method signatures, so return Future instead of ListenableFuture
   * or SettableFuture.
   */
  public Future<E> getFuture() {
    return transform(
        futureEval,
        new Function1<Evaluation, E>() {
          @Override
          public E invoke(Evaluation e) {
            return atom.transform(e);
          }
        },
        DirectExecutor.INSTANCE);
  }

  /** Blocks until the atom has completed execution. */
  public E get() throws ExecutionException, InterruptedException {
    checkState(Looper.myLooper() != Looper.getMainLooper(), "On main thread!");
    return getFuture().get();
  }

  /** Blocks until the atom has completed execution with a configurable timeout. */
  public E get(long val, TimeUnit unit)
      throws ExecutionException, InterruptedException, TimeoutException {
    checkState(Looper.myLooper() != Looper.getMainLooper(), "On main thread!");
    return getFuture().get(val, unit);
  }

  @Override
  public String getId() {
    return ID;
  }

  @Override
  public IBinder getIBinder() {
    return atomActionResultPropagator.asBinder();
  }

  @Override
  public void setIBinder(IBinder binder) {
    atomActionResultPropagator = IAtomActionResultPropagator.Stub.asInterface(binder);
  }
}