Giter Club home page Giter Club logo

okreplay's Introduction

OkReplay

Automatically record and replay OkHttp network interaction through your Android application. This project was based on the great Betamax library - which was inspired by Ruby's awesome VCR gem.

Introduction

You don’t want 3rd party downtime, network issues or resource constraints (such as the Twitter API’s rate limit) to break your tests. Writing custom stub web server code and configuring the application to connect to a different URI when under test is tedious and might not accurately simulate the real service.

OkReplay aims to solve these problems by intercepting HTTP connections initiated by your application and replaying previously recorded responses.

The first time a test annotated with @OkReplay is run, any HTTP traffic is recorded to a tape and subsequent test runs will play back the recorded HTTP response from the tape without actually connecting to the external server.

OkReplay works with JUnit and Espresso. OkReplay can be used to test any Java or Android applications, provided they are using an OkHttpClient to make requests.

Tapes are stored to disk as YAML files and can be modified (or even created) by hand and committed to your project’s source control repository so they can be shared by other members of your team and used by your CI server. Different tests can use different tapes to simulate various response conditions. Each tape can hold multiple request/response interactions. An example tape file can be found here.

Usage

OkReplay comes as an OkHttp Interceptor. When "started", responses are served from the Tape file when a match is found for the MatchRule and the TapeMode is readable. If the Tape is writable, responses will be served from the network as usual and the interaction will be stored on a Tape.

Add the OkReplayInterceptor to your OkHttpClient:

OkReplayInterceptor okReplayInterceptor = new OkReplayInterceptor();
OkHttpClient client = new OkHttpClient.Builder()
  .addInterceptor(okReplayInterceptor)
  .build()

By default the interceptor won't do anything unless it's explicitly started.

Espresso integration

In your instrumentation test class, add:

private final ActivityTestRule<MainActivity> activityTestRule =
      new ActivityTestRule<>(MainActivity.class);
  private final OkReplayConfig configuration = new OkReplayConfig.Builder()
      .tapeRoot(new AndroidTapeRoot(getContext(), getClass()))
      .defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY
      .sslEnabled(true)
      .interceptor(okReplayInterceptor))
      .build();
  @Rule public final TestRule testRule =
      new OkReplayRuleChain(configuration, activityTestRule).get();

  @Test
  @OkReplay
  public void testFooBar() {
    // write your test as usual...
  }

IMPORTANT: If you already have one, remove the @Rule annotation from your ActivityTestRule.

Gradle plugin integration

Add the classpath and apply the plugin in your build.config:

buildscript {
  repositories {
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
  }
  dependencies {
    classpath 'com.airbnb.okreplay:gradle-plugin:1.5.0'
  }
}

apply plugin: 'okreplay'

You should now see these two tasks when you run ./gradlew tasks:

clearDebugOkReplayTapes - Remove OkReplay tapes from the device
pullDebugOkReplayTapes - Pull OkReplay tapes from the device

Download

Download the latest JAR or grab via Maven:

<dependency>
  <groupId>com.airbnb.okreplay</groupId>
  <artifactId>okreplay</artifactId>
  <version>1.5.0</version>
</dependency>

or Gradle:

debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'

Snapshots of the development version are available in Sonatype's snapshots repository.

License

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.

okreplay's People

Contributors

ankit-mnnit avatar auhlig avatar chaitanyazooms avatar cowboygneox avatar dkowis avatar erdi avatar felipecsl avatar fkorotkov avatar froots avatar georgecodes avatar gitter-badger avatar jamesnetherton avatar jlecount-okta avatar jschear avatar lhotari avatar meoyawn avatar mirelon avatar nobusue avatar nuxusr avatar pledbrook avatar premnirmal avatar rhart avatar robfletcher avatar ronocod avatar rossbacher avatar rschmitt avatar technoir42 avatar tmtrademarked avatar veyndan avatar vorburger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

okreplay's Issues

Сan't find UTF-8 charset

@Override public final String bodyAsText() {
try {
return new String(body(), getCharset());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

Caused by: java.lang.RuntimeException: java.io.UnsupportedEncodingException: java.nio.charset.CharsetICU[UTF-8]
  at okreplay.AbstractMessage.bodyAsText(AbstractMessage.java:50)
  at okreplay.RecordedMessage.maybeBodyAsString(RecordedMessage.java:49)
  at okreplay.RecordedRequest.toYaml(RecordedRequest.java:55)
  at okreplay.RecordedRequest.toYaml(RecordedRequest.java:18)
  at okreplay.RecordedInteraction.toYaml(RecordedInteraction.java:30)
  at okreplay.MemoryTape.record(MemoryTape.java:139)
  at okreplay.YamlTape.record(YamlTape.java:15)
  at okreplay.OkReplayInterceptor.intercept(OkReplayInterceptor.java:54)
Caused by: java.io.UnsupportedEncodingException: java.nio.charset.CharsetICU[UTF-8]
    at java.nio.charset.Charset.forNameUEE(Charset.java:322)
    at java.lang.StringFactory.newStringFromBytes(StringFactory.java:63)
    at okreplay.AbstractMessage.bodyAsText(AbstractMessage.java:48)

Ok, so apparently this is what happens: Charset.forName("java.nio.charset.CharsetICU[UTF-8]"), which is definitely not what you want.
It happens because of the toString here:

return Optional.fromNullable(MediaType.parse(header).charset()).or(UTF_8).toString();

Gradle plugin checks for connected devices at configuration time

Hi! I've noticed that when gradle is configuring my app project (which has the OkReplay gradle plugin applied), I see this output in the console if I don't have any android devices connected:

> Configure project :app  
No connected devices!
No connected devices!
No connected devices!

(We have three build variants.)

I think what's happening is that when the OkReplay tasks are setup, a DeviceBridge is created, which eagerly calls ConnectedDeviceProvider#init(), which in turn uses adb to check for connected devices.

I think this call to init()should probably be moved out of the configuration stage, and into the tasks themselves (it looks like this is what the Android Gradle Plugin does with DeviceProviderInstrumentTestTask by using the newer DeviceProvider.use API). I imagine this will cut down configuration time a tiny bit for all of our builds.

If y'all agree, I'm happy to open a PR to do so.

Replace data in yaml file with fake data in @Before setUp() method

Hi there. We usually replace data in yaml file with fake data and it works perfectly if it is in the tests. Okreplay records tape with real request/response and we just replace it afterwards with fake data and use it in READ_ONLY mode.
In the test we just type fakeuser/fakepassword and everything works fine.

But when we do the same thing in the setUp() method with annotation @before (for example, everytime we log in to the app and then do the tests), it works good with original real data but if we change it, it fails.

To make OkReplayInterceptor open again.

Hello,
In the version 1.5.0 you moved OkReplayInterceptor class to Kotlin and now it's not possible to extend it. Since OkReplayConfig.Builder().interceptor() method accepts only OkReplayInterceptor type it's no longer possible to supply customized interceptor to it.
Please consider making OkReplayInterceptor class open.
Thank you.

Tasks fail in a project using version 4.0.0 of the Android Gradle Plugin

After upgrading to AGP 4.0.0, our clear<variant>OkReplayTapes task is failing with a NoClassDefFoundError. It looks like ConnectedDeviceProvider was moved to the com.android.build.gradle.internal.testing package.

I tried upgrading the AGP dependency in OkReplay, but it's going to take a bit of work -- it requires Gradle 6.1.1 at a minimum, but https://github.com/groovy/groovy-android-gradle-plugin isn't compatible with Gradle 6 (and it is no longer maintained), so the tests in the okreplay-tests project will probably need to be migrated to Java or Kotlin.

I think the best move here is to access the device providers through AGP'sBaseExtension#deviceProviders API instead of instantiating them directly, which looks like it should work on AGP 3.6.0 and 4.0.0. I'm working on a PR to do that.

Stacktrace:

jschear ~/dev/slack-android-ng (master) $ ./gradlew app:clearInternalDebugOkReplayTapes --stacktrace
> Task :app:clearInternalDebugOkReplayTapes FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:clearInternalDebugOkReplayTapes'.
> com/android/builder/testing/ConnectedDeviceProvider

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:clearInternalDebugOkReplayTapes'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:207)
        at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:263)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:205)
        ...
Caused by: java.lang.NoClassDefFoundError: com/android/builder/testing/ConnectedDeviceProvider
        at okreplay.DeviceBridge.<init>(DeviceBridge.kt:10)
        at okreplay.DeviceBridgeProvider$Companion.get$okreplay_gradle_plugin(DeviceBridgeProvider.kt:15)
        at okreplay.ClearTapesTask.clearTapes$okreplay_gradle_plugin(ClearTapesTask.kt:16)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:104)
        ...

@Before tapes

Our current tests put the app into a starting state during the @before phase. It would be nice if that could be saved in it's own tape. Currently, a no-op @test results in 1.2mb yaml which is mostly duplicated between all the other tests.

Cannot pull tapes due to wrong package directory

It seems that the pull and clear tapes Gradle tasks in the most recent version are looking for /sdcard/okreplay/tapes/com.domain.app.test, but the actual tapes are in /sdcard/okreplay/tapes/com.domain.app. This means they don't accomplish anything.

Multiple flavours wrong pull directory

I have two flavours of app: main and chain. Main has applicationId = "com.domain.app" and Chain has applicationId = "com.domain.app.chain" . When i try to pull tapes it pulls from /sdcard/okreplay/tapes/com.domain.app.chain.test/ and when i comment Chain flavour in gradle it pulls from /sdcard/okreplay/tapes/com.domain.app.test/ like it should be. Also folder on sd card is named com.domain.app.chain.test no matter if Chain is commented or not in gradle. Does this library has support for multiple flavours?

Build issue with customized okreplay

My app doesn't use okhttp in the network layer so I have to copy the okreplay implementation into my project and make some modifications. Now the app fails to compile because yaml has dependency on java.beans.IntrospectionException but Android didn't package that class into release (https://stackoverflow.com/questions/5488236/is-there-a-work-around-for-the-android-error-unable-to-resolve-virtual-method-j)
Compile error:

demo_android_espresso_idlingresource_okreplay/app/src/main/java/com/elyeproj/okreplay/TapeRepresenter.java:78: error: cannot access IntrospectionException
        Set<Property> properties = super.createPropertySet(type, bAccess);
                                                          ^
  class file for java.beans.IntrospectionException not found
1 error

Sample app:
demo_android_espresso_idlingresource_okreplay.zip
Command to run:
./gradlew installDebug

READ_ONLY mode is actually not read-only

We found a strange failure in one of our tests recently. For some reason, our test failed when trying to connect to a server. That's unexpected, because the test was running in TapeMode READ_ONLY.

It turns out with the current implementation of OkReplayInterceptor, a read-only tape will still make the server call if a match is not found on a particular tape. This is bad, because it introduces flakiness if the server is non-responsive. That's not what we want in a READ_ONLY mode.

#68 should address this, if desired.

Make it work with androidx

This needs to get up to speed so we can use the new androidx framework... androidx.test.espresso.intent.rule

Publish Sources Jar

Can you please publish the sources artifact. This makes it easier to debug into the library code.

Cannot build project

A problem occurred configuring project ':okreplay-core'.
> Could not resolve all artifacts for configuration ':okreplay-core:classpath'.
   > Could not find com.vanniktech:gradle-maven-publish-plugin:0.9.0-SNAPSHOT.
     Searched in the following locations:
       - https://jcenter.bintray.com/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/maven-metadata.xml
       - https://jcenter.bintray.com/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.pom
       - https://jcenter.bintray.com/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.jar
       - file:/Users/eric.liu/.m2/repository/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/maven-metadata.xml
       - file:/Users/eric.liu/.m2/repository/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.pom
       - file:/Users/eric.liu/.m2/repository/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.jar
       - https://oss.sonatype.org/content/repositories/snapshots/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/maven-metadata.xml
       - https://oss.sonatype.org/content/repositories/snapshots/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.pom
       - https://oss.sonatype.org/content/repositories/snapshots/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.jar
       - https://maven.google.com/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/maven-metadata.xml
       - https://maven.google.com/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.pom
       - https://maven.google.com/com/vanniktech/gradle-maven-publish-plugin/0.9.0-SNAPSHOT/gradle-maven-publish-plugin-0.9.0-SNAPSHOT.jar
     Required by:
         project :okreplay-core

Possible solution:
 - Declare repository providing the artifact, see the documentation at https://docs.gradle.org/current/userguide/declaring_repositories.html

Cannot query the value of property 'applicationId'

Caused by: java.lang.IllegalStateException: Cannot query the value of property 'applicationId' because configuration of project ':app' has not completed yet.
        at org.gradle.api.internal.provider.AbstractProperty$NonFinalizedValue.maybeFinalizeOnRead(AbstractProperty.java:368)
        at org.gradle.api.internal.provider.AbstractProperty.beforeRead(AbstractProperty.java:229)
        at org.gradle.api.internal.provider.AbstractProperty.calculateOwnValue(AbstractProperty.java:126)
        at org.gradle.api.internal.provider.AbstractMinimalProvider.get(AbstractMinimalProvider.java:84)
        at com.android.build.gradle.internal.api.BaseVariantImpl.getApplicationId(BaseVariantImpl.java:245)
        at com.android.build.gradle.internal.api.TestVariantImpl_Decorated.getApplicationId(Unknown Source)
        at okreplay.OkReplayPlugin$Companion.testApplicationId(OkReplayPlugin.kt:102)
        at okreplay.OkReplayPlugin$Companion.access$testApplicationId(OkReplayPlugin.kt:88)
        at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:68)
        at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:14)
        at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:123)
...

All I did was add
classpath 'com.airbnb.okreplay:gradle-plugin:1.5.0'
and
apply plugin: 'okreplay'
to build.gradle. AS 2020.3.1 patch 3, com.android.tools.build:gradle:7.0.3.

Support for custom match rules

Sometimes the default match rules just aren't enough. There should be support for setting custom match rules. For example, some queries have time information in them, which would need to be stripped before comparing bodies. I'll see if I can get a pull request started perhaps.

Transitive dependency on vulnerable `org.yaml:snakeyaml`

Per GHSA-mjmj-j48q-9wg2, all org.yaml:snakeyaml versions before 2.0 have a serious vulnerability.

The latest release of okreplay library bundles such a vulnerable org.yaml:snakeyaml version, and it is not possible to override the version of org.yaml:snakeyaml manually without breaking okreplay (to be expected when trying to force-use a different major version of org.yaml:snakeyaml).

Allow support for scrubbing sensitive data from tapes

We're using OkReplay to write UI tests, and it's been working well for us so far! But one problem we've had with the library is that the generated tapes sometimes contain sensitive information like passwords/tokens/etc which we would prefer not to check in.

One solution that feels reasonable would be to allow us to create a TapeRoot which implements a Reader/Writer that knows how to replace sensitive data with safe tokens and vice versa. Unfortunately, AndroidTapeRoot is final, and the PermissionRule has a hard requirement of an AndroidTapeRoot instance.

Is there a better solution available for scrubbing out sensitive data before storing the tape? Alternately, could we just make AndroidTapeRoot non-final? Thoughts?

Remove Guava Dependency

The okreplay artifact has a dependency on Guava. This has a big impact on method count, and will easily take an app over the limits of single dex. This is something we try to avoid on my team, and makes the library unusable. How many things does okreplay need from Guava? I'm sure you can easily replace these and remove this dependency.

WRITE_EXTERNAL_STORAGE Permission on newer devices

When trying to run the sample tests on my Nexus 6P running Android 7.1.2 I get the following RuntimeException:

We need WRITE_EXTERNAL_STORAGE permission for OkReplay. Please add adbOptions { installOptions "-g" } to your build.gradle file.

This option is already in the build.gradle file of the sample.

Wrong Maven coordinates and download link in readme

It looks like the Download section in the readme has a couple of typos.

Looking at http://search.maven.org/#search%7Cga%7C1%7Ccom.airbnb.okreplay I can see these artifacts:

  • noop
  • junit
  • espresso
  • okreplay
  • gradle-plugin

In the readme, the link to "Download the latest JAR" points at https://search.maven.org/remote_content?g=com.airbnb.okreplay&a=core&v=LATEST but it appears the artifact is now okreplay instead of core.

The provided Maven coordinates also don't work for me, eg the Maven example has artifactId okreplay-core, but it seems to be published as okreplay, as above.

Likewise the 2nd and 3rd lines in the Gradle example should have the leading okreplay- removed by the looks of things.

Possible to get a new release cut?

It looks like there's some new work going into this library, which is great to see. But I wanted to see if we could get a timeline from the maintainers on a possible release. Continuing to depend on a snapshot version is no longer tenable for us, and we'd like to use a stable version if possible. If there are no plans for a release any time soon, it would be nice to know that so we can evaluate our options.

Thank you for all the work you've put into this library - it's a wonderful contribution to the Android ecosystem and much appreciated by those of use who use it.

Java library

Can this project be utilized in a Java library? The API seems heavily Android-based.

Work together with RoboElectric

Hi there,

Seems its not possible to get this to play nicely with RoboElectric - or am I missing something ?

It would be quite EPIC if RoboElectric and okreplay could work together - making even faster and better tests.

Gradle plugin 1.3.0 NoSuchElement exception

Trace: * Exception is:
org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':app'.
at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:94)
at org.gradle.configuration.project.LifecycleProjectEvaluator.notifyAfterEvaluate(LifecycleProjectEvaluator.java:89)
at org.gradle.configuration.project.LifecycleProjectEvaluator.doConfigure(LifecycleProjectEvaluator.java:76)
at org.gradle.configuration.project.LifecycleProjectEvaluator.access$000(LifecycleProjectEvaluator.java:33)
at org.gradle.configuration.project.LifecycleProjectEvaluator$1.execute(LifecycleProjectEvaluator.java:53)
at org.gradle.configuration.project.LifecycleProjectEvaluator$1.execute(LifecycleProjectEvaluator.java:50)
at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:61)
at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:50)
at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:628)
at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:129)
at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:35)
at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:98)
at org.gradle.execution.TaskSelector.getSelection(TaskSelector.java:81)
at org.gradle.execution.commandline.CommandLineTaskParser.parseTasks(CommandLineTaskParser.java:42)
at org.gradle.execution.TaskNameResolvingBuildConfigurationAction.configure(TaskNameResolvingBuildConfigurationAction.java:44)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:48)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$000(DefaultBuildConfigurationActionExecuter.java:25)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter$1.proceed(DefaultBuildConfigurationActionExecuter.java:54)
at org.gradle.execution.DefaultTasksBuildExecutionAction.configure(DefaultTasksBuildExecutionAction.java:44)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:48)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter.access$000(DefaultBuildConfigurationActionExecuter.java:25)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter$1.proceed(DefaultBuildConfigurationActionExecuter.java:54)
at org.gradle.execution.ExcludedTaskFilteringBuildConfigurationAction.configure(ExcludedTaskFilteringBuildConfigurationAction.java:47)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter.configure(DefaultBuildConfigurationActionExecuter.java:48)
at org.gradle.execution.DefaultBuildConfigurationActionExecuter.select(DefaultBuildConfigurationActionExecuter.java:36)
at org.gradle.initialization.DefaultGradleLauncher$2.execute(DefaultGradleLauncher.java:185)
at org.gradle.initialization.DefaultGradleLauncher$2.execute(DefaultGradleLauncher.java:182)
at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:56)
at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:182)
at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:119)
at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:102)
at org.gradle.launcher.exec.GradleBuildController.run(GradleBuildController.java:71)
at org.gradle.tooling.internal.provider.runner.BuildModelActionRunner.run(BuildModelActionRunner.java:50)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.tooling.internal.provider.runner.RunAsBuildOperationBuildActionRunner$1.execute(RunAsBuildOperationBuildActionRunner.java:43)
at org.gradle.tooling.internal.provider.runner.RunAsBuildOperationBuildActionRunner$1.execute(RunAsBuildOperationBuildActionRunner.java:40)
at org.gradle.internal.Transformers$4.transform(Transformers.java:169)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:106)
at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:56)
at org.gradle.tooling.internal.provider.runner.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:40)
at org.gradle.tooling.internal.provider.runner.SubscribableBuildActionRunner.run(SubscribableBuildActionRunner.java:75)
at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:41)
at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:26)
at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:75)
at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:49)
at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:44)
at org.gradle.tooling.internal.provider.ServicesSetupBuildActionExecuter.execute(ServicesSetupBuildActionExecuter.java:29)
at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:47)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
at org.gradle.util.Swapper.swap(Swapper.java:38)
at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.util.NoSuchElementException: Collection is empty.
at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:168)
at okreplay.OkReplayPlugin.testApplicationId(OkReplayPlugin.kt:99)
at okreplay.OkReplayPlugin.access$testApplicationId(OkReplayPlugin.kt:15)
at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:72)
at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:15)
at org.gradle.api.internal.DefaultDomainObjectCollection.all(DefaultDomainObjectCollection.java:117)
at okreplay.OkReplayPlugin$applyPlugin$1.execute(OkReplayPlugin.kt:65)
at okreplay.OkReplayPlugin$applyPlugin$1.execute(OkReplayPlugin.kt:15)
at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:93)
at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:82)
at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:44)
at org.gradle.internal.event.BroadcastDispatch.dispatch(BroadcastDispatch.java:79)
at org.gradle.internal.event.BroadcastDispatch.dispatch(BroadcastDispatch.java:30)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy16.afterEvaluate(Unknown Source)
at org.gradle.configuration.project.LifecycleProjectEvaluator.notifyAfterEvaluate(LifecycleProjectEvaluator.java:82)
... 79 more

Using classpath 'com.android.tools.build:gradle:2.3.3' gradle. Getting this exception for one specific flavour of my app, I have:

buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.airbnb.okreplay:gradle-plugin:1.3.0'
}
}

android {
buildTypes {
debug{} release {} releaseDebuggable{}
}
productFlavours { dev{} prod{} }
}
...
androidTestCompile 'com.airbnb.okreplay:espresso:1.3.0'
debugCompile group: 'com.airbnb.okreplay', name: 'okreplay', version: '1.3.0'
releaseCompile group: 'com.airbnb.okreplay', name: 'noop', version: '1.3.0'

The devDebug and prodRelease flavours assemble and install just fine, the cAT job runs the tests, all fine here. But the devReleaseDebuggable flavour is getting this exception.

Are you interested in help to create support for Junit5?

First of all, thank you for creating and maintaining code in the OkReplay repo.

For those who do not see it, the okreplay-junit lib is meant for junit4. This because how rules are applied and in combination with the annotation @OkReplay.

I have recently created support for junit5 in another project and are wondering if you would be interested in having me sharing it in your repo. It could need some rewriting to be more generic, but I would be interested in trying to create a PR with it for you.

Looking forward to hearing back from you. @rossbacher @cowboygneox @felipecsl

Problems running okreplay gradle plugin with Android Studio 3.1

Having problems running the Android Studio 3.1 Canary 7 and 8 build. So gradle version is 4.4, gradle plugin is com.android.tools.build:gradle:3.1.0-alpha07.
If running with "apply plugin: 'okreplay" the gradle configuration stage gets stuck on connecting to adb (even if no simulator or devices are running), the log prints this in an eternal loop:

[DeviceMonitor]: Unable to open connection to: localhost/10.128.0.62:5037, due to: java.net.ConnectException: Operation timed out
[DeviceMonitor]: Connection attempts: 1

Everything runs fine when disabling the okreplay plugin.

Also tried clearing gradle caches and killing adb. The gradle debug log does not show any more information than the error above.

Make NonWritableTapeException more helpful

Output something nice like VCR:

================================================================================
An HTTP request has been made that VCR does not know how to handle:
  GET http://example.com/

There is currently no cassette in use. There are a few ways
you can configure VCR to handle this request:

  * If you want VCR to record this request and play it back during future test
    runs, you should wrap your test (or this portion of your test) in a
    `VCR.use_cassette` block [1].
  * If you only want VCR to handle requests made while a cassette is in use,
    configure `allow_http_connections_when_no_cassette = true`. VCR will
    ignore this request since it is made when there is no cassette [2].
  * If you want VCR to ignore this request (and others like it), you can
    set an `ignore_request` callback [3].

[1] https://www.relishapp.com/myronmarston/vcr/v/2-0-0/docs/getting-started
[2] https://www.relishapp.com/myronmarston/vcr/v/2-0-0/docs/configuration/allow-http-connections-when-no-cassette
[3] https://www.relishapp.com/myronmarston/vcr/v/2-0-0/docs/configuration/ignore-request
================================================================================

OkReplay Annotation is too strict

It takes an array of MatchRules. It should be an array of MatchRule so we can pass in custom rules and not one from the default set.

Requests indexation

Hi, firstly, thank you so much for a good library!

OkReplay covers all our needs, but there is one more feature which could be useful: Request indexation.

For instance, we have request a, which we execute and record.
During the test run, we may have the the same request a, which returns other response, but all requests params are the same.

In that case, looks like, it wouldn't be recorded, and during replay only first response would be substituted for all requests with the same match.
(Please correct me if I wrong)

Subsequent mode can't be used in that case, as we may have 100 requests at the same time and we don't have a guarantee that they are subsequent.

Do you have any plans about it?
If not, would you accept the contribution?

Thank you!

Push Ok Replay Tapes Gradle Task

To create certain scenarios it's convenient to first run a test to record a tape, then pull it, modify it, then push it back to the device so it replays the modified tape. Would be great to have a task to push tapes from resources/tapes to the device. Or is there some other workflow I'm supposed to be using? It's not exactly clear from the documentation.

HTTP Message must be set for OkHttp 3.8.0

OkHttpResponseAdapter needs to set the message field to ensure that it is compatible with OkHttp 3.8.0 and later.

From OkHttp's Changelog:

The response message is now non-null. This is the "Not Found" in the status line "HTTP 404 Not Found". If you are building responses programmatically (with new Response.Builder()) you must now always supply a message. An empty string "" is permitted. This value was never null on responses returned by OkHttp itself, and it was an old mistake to permit application code to omit a message.

Sample stack trace:

java.lang.IllegalStateException: message == null
   at okhttp3.Response$Builder.build(Response.java:431)
   at okreplay.OkHttpResponseAdapter.adapt(OkHttpResponseAdapter.java:20)
   at okreplay.OkReplayInterceptor.intercept(OkReplayInterceptor.java:40)
   at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
   at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain
   ...

Questions: How is this library maintained? ...

Hello,

I was about to write something similar record/replay for OkHttp and I found your library, which I'm quite happy about, because I don't want to reinvent the wheel and the tool looks great, however I have few questions.

  1. Is this something Airbnb uses internally? It is not on GitHub under the Airbnb organization, has many commits and contributions so I'm curious why.
  2. Is this maintained here or is it just a mirror for Airbnb tool elsewhere? This repo doesn't look much active so I'm curious if Airbnb maintains it somewhere else.

Thank you for answer.

Josef

Sequential request matching with rules

It seems like it should be possible to write a sequential replay test where I can match rules, but record multiple interactions per rule set. This would help by not enforcing strict ordering of requests like a pure sequential test, but would allow repeated queries to an endpoint with differing responses each time.

It seems like the best behavior for this type of test would be to record rules to tape as long as READ_WRITE is active and then parse the rules when READ_ONLY is active. You would consume the rules sequentially until only one is left and then reuse it for all future requests which match that rule set.

I'm not sure how hard this would be. I may take a look at implementing it.

Make Optional public

Hi,
I'm using a custom RecorderRule which extends Recorder and implements TestRule. This is a nice, convenient way to provide some test setup/cleanup without code repetition. The problem is that Recorder method public void start(String tapeName, Optional<TapeMode> mode, Optional<MatchRule> matchRule) uses Optional - which is package private so this method cannot be called. Is there a reason behind this? If not, making Optional public would solve that issue. Or maybe switching to Kotlin's optional would be another way. Or maybe this method could accept MatchRule other way than wrapped around optional.
Let me know what you think.

ConcurrentModificationException

Here's the stack trace:

java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at org.yaml.snakeyaml.representer.BaseRepresenter.representSequence(BaseRepresenter.java:132)
at org.yaml.snakeyaml.representer.SafeRepresenter$RepresentList.representData(SafeRepresenter.java:173)
at org.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:94)
at org.yaml.snakeyaml.representer.Representer.representJavaBeanProperty(Representer.java:125)
at okreplay.TapeRepresenter.representJavaBeanProperty(TapeRepresenter.java:40)
at org.yaml.snakeyaml.representer.Representer.representJavaBean(Representer.java:83)
at org.yaml.snakeyaml.representer.Representer$RepresentJavaBean.representData(Representer.java:49)
at org.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:105)
at org.yaml.snakeyaml.representer.BaseRepresenter.represent(BaseRepresenter.java:64)
at org.yaml.snakeyaml.Yaml.dumpAll(Yaml.java:242)
at org.yaml.snakeyaml.Yaml.dump(Yaml.java:221)
at okreplay.YamlTapeLoader.writeTo(YamlTapeLoader.java:75)
at okreplay.YamlTapeLoader.writeTape(YamlTapeLoader.java:52)
at okreplay.Recorder.stop(Recorder.java:68)
at okreplay.RecorderRule$apply$1.evaluate(RecorderRule.kt:44)
at okreplay.PermissionRule$apply$1.evaluate(PermissionRule.kt:18)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:262)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1932)

Yaml file was not generated after test run.

I added all the dependencies and annotations as mentioned in the wiki to my espresso test.
Here are the steps I followed to run a single test via command line:

  1. Install apk via gradlew:
    ./gradlew install

  2. Install test apk via gradlew:
    ./gradlew installAndroidTest

  3. Run single test via adb:
    ./adb shell am instrument -w -r -e class com.xxx.Tests#testFunction com.xxx.test/com.xxx.base.AndroidInstrumentationTestRunner

The test runs successfully but I don't see the Yaml file being generated under androidTest->assets -> tapes. Any thoughts?

Cannot use plugin 1.5.0 with AGP 3.4

We currently use AGP 3.4.2 and cannot update to 3.5.0 because DexGuard doesn't support it yet.
When we apply OkReplay 1.5.0 it pulls AGP 3.5.0 since that's an implementation dependency of OkReplay plugin and our build fails with a message like:

DexGuard Gradle plugin: Android Gradle plugin version is not supported. (3.5.0), supported versions include [2.3.0:3.4.*].

To avoid this I think we should change AGP dependency from implementation to compileOnly. I'm working on a fix right now.

NoSuchMethodError (GlobalScope.getAndroidBuilder) with android gradle plugin 3.5.0-beta01

Upon switching to 'com.android.tools.build:gradle:3.5.0-beta01' & gradle-5.4-rc-1-all.zip, gradle config fails in our project with the error below.

Culprit is okreplay plugin (we are on the latest 1.4 version)

relevant snippet:

Caused by: java.lang.NoSuchMethodError: com.android.build.gradle.internal.scope.GlobalScope.getAndroidBuilder()Lcom/android/builder/core/AndroidBuilder;
        at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:67)

full stack trace:

| => ./gradlew app:tasks --stacktrace

> Configure project :app
Configured with minSdk = 19.
[android.testOptions]: Running tests without Android Test Orchestrator
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring project ':app'.
> Failed to notify project evaluation listener.
   > com.android.build.gradle.internal.scope.GlobalScope.getAndroidBuilder()Lcom/android/builder/core/AndroidBuilder;

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':app'.
        at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:79)
        at org.gradle.configuration.project.LifecycleProjectEvaluator.addConfigurationFailure(LifecycleProjectEvaluator.java:72)
        at org.gradle.configuration.project.LifecycleProjectEvaluator.access$600(LifecycleProjectEvaluator.java:53)
        at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:198)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject$1.run(LifecycleProjectEvaluator.java:111)
        at org.gradle.internal.Factories$1.create(Factories.java:25)
        at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:183)
        at org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:40)
        at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:226)
        at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:220)
        at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withMutableState(DefaultProjectStateRegistry.java:186)
        at org.gradle.configuration.project.LifecycleProjectEvaluator$EvaluateProject.run(LifecycleProjectEvaluator.java:95)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:67)
        at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:695)
        at org.gradle.api.internal.project.DefaultProject.evaluate(DefaultProject.java:143)
        at org.gradle.execution.TaskPathProjectEvaluator.configure(TaskPathProjectEvaluator.java:35)
        at org.gradle.execution.TaskPathProjectEvaluator.configureHierarchy(TaskPathProjectEvaluator.java:62)
        at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:41)
        at org.gradle.initialization.DefaultGradleLauncher$ConfigureBuild.run(DefaultGradleLauncher.java:302)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.initialization.DefaultGradleLauncher.configureBuild(DefaultGradleLauncher.java:210)
        at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:151)
        at org.gradle.initialization.DefaultGradleLauncher.executeTasks(DefaultGradleLauncher.java:134)
        at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:58)
        at org.gradle.internal.invocation.GradleBuildController$1.execute(GradleBuildController.java:55)
        at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:82)
        at org.gradle.internal.invocation.GradleBuildController$3.create(GradleBuildController.java:75)
        at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:183)
        at org.gradle.internal.work.StopShieldingWorkerLeaseService.withLocks(StopShieldingWorkerLeaseService.java:40)
        at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:75)
        at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:55)
        at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:31)
        at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
        at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:58)
        at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
        at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:39)
        at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:51)
        at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:45)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
        at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:45)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:49)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter$1.transform(InProcessBuildActionExecuter.java:46)
        at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:78)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:46)
        at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:31)
        at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:42)
        at org.gradle.launcher.exec.BuildTreeScopeBuildActionExecuter.execute(BuildTreeScopeBuildActionExecuter.java:28)
        at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:78)
        at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:52)
        at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:59)
        at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:36)
        at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:68)
        at org.gradle.tooling.internal.provider.SessionScopeBuildActionExecuter.execute(SessionScopeBuildActionExecuter.java:38)
        at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:37)
        at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:26)
        at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:43)
        at org.gradle.tooling.internal.provider.ParallelismConfigurationBuildActionExecuter.execute(ParallelismConfigurationBuildActionExecuter.java:29)
        at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:60)
        at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:32)
        at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:55)
        at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:41)
        at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:48)
        at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:32)
        at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:67)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
        at org.gradle.util.Swapper.swap(Swapper.java:38)
        at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:62)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:81)
        at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
        at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
        at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
        at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:295)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
Caused by: org.gradle.internal.event.ListenerNotificationException: Failed to notify project evaluation listener.
        at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:86)
        at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:324)
        at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:234)
        at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:140)
        at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
        at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
        at com.sun.proxy.$Proxy25.afterEvaluate(Unknown Source)
        at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:190)
        at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate$1.execute(LifecycleProjectEvaluator.java:187)
        at org.gradle.api.internal.project.DefaultProject.stepEvaluationListener(DefaultProject.java:1424)
        at org.gradle.configuration.project.LifecycleProjectEvaluator$NotifyAfterEvaluate.run(LifecycleProjectEvaluator.java:196)
        ... 112 more
Caused by: java.lang.NoSuchMethodError: com.android.build.gradle.internal.scope.GlobalScope.getAndroidBuilder()Lcom/android/builder/core/AndroidBuilder;
        at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:67)
        at okreplay.OkReplayPlugin$applyPlugin$1$1.execute(OkReplayPlugin.kt:14)
        at org.gradle.api.internal.DefaultDomainObjectCollection.all(DefaultDomainObjectCollection.java:158)
        at okreplay.OkReplayPlugin$applyPlugin$1.execute(OkReplayPlugin.kt:62)
        at okreplay.OkReplayPlugin$applyPlugin$1.execute(OkReplayPlugin.kt:14)
        at org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction$1$1.run(DefaultListenerBuildOperationDecorator.java:150)
        at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.reapply(DefaultUserCodeApplicationContext.java:58)
        at org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction$1.run(DefaultListenerBuildOperationDecorator.java:147)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:402)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:394)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:92)
        at org.gradle.configuration.internal.DefaultListenerBuildOperationDecorator$BuildOperationEmittingAction.execute(DefaultListenerBuildOperationDecorator.java:144)
        at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:91)
        at org.gradle.internal.event.BroadcastDispatch$ActionInvocationHandler.dispatch(BroadcastDispatch.java:80)
        at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:42)
        at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:230)
        at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:149)
        at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:58)
        ... 122 more

Update to latest espresso?

Hey there! We're trying to update our app to use Espresso 3.2, and it seems like OkReplay currently blocks this upgrade because the dependency on Espresso is strict. Possibly this is due to okreplay-espresso using an API dependency on espresso:3.1.0.

Is there any chance this library can be updated to use a more modern espresso, or at least to not prohibit such? I'm not sure how much work that would involve, but it seems like it might make it easier for applications to continue using it.

Failed to create the directory for tapes. Is your sdcard directory read-only?

Getting the message:

Failed to create the directory for tapes. Is your sdcard directory read-only?

on my Nexus 6P, and the Pixel. I tried to debug, the relevant source is in AndroidTapeRoot.kt

internal fun grantPermissionsIfNeeded() {
    val res = assetManager.context.checkCallingOrSelfPermission(WRITE_EXTERNAL_STORAGE)
    if (res != PackageManager.PERMISSION_GRANTED) {
      throw RuntimeException("We need WRITE_EXTERNAL_STORAGE permission for OkReplay. " +
          "Please add `adbOptions { installOptions \"-g\" }` to your build.gradle file.")
    }
    root.mkdirs()
    if (!root.exists()) {
      throw RuntimeException("Failed to create the directory for tapes. "
          + "Is your sdcard directory read-only?")
    }
    setWorldWriteable(root)
  }

My SD card is writeable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.