It’s more than 4 years since we launched the initial variant of emulator.wtf. Over this time we’ve made a ton of improvements to the system - initially we didn’t even have any way to view test results on the emulator.wtf web app or a way for users to sign up!

We think a 1.0 version of everything is long overdue. Initially we wanted to have the 0.x versions as an escape hatch to make breaking changes to either the CLI or integration interfaces. The interface has now stabilized enough that we’re happy to call it 1.0. There’s also a feeling of “pre-production” associated with a 0.x version which just isn’t true anymore.

The 1.0 version also brings us to an opportunity to make a few breaking changes in all our integrations. We’re shipping 1.0 release candidate versions with them over the next few days. For details, see below.


Breaking changes in 1.0

Video recording is now enabled by default

Video capture is immensely helpful when debugging UI tests. As emulator.wtf does all the capturing and encoding outside the emulator process there’s really no performance hit either. Starting from 1.0.0-rc01 video recording will be on by default in all our integrations.

If you do not want to capture video, you can still disable the recording by explictly disabling it using one of the options below:

  • --no-record-video (CLI)
  • recordVideo.set(false) (Gradle)
  • record-video: false (GitHub actions)

GPU acceleration is now enabled by default

We did measurements when we launched GPU-acceleration a bit over two years ago. Results were very clear: in all cases there’s a substantial improvement in performance, even when the tests do not have any UI component to them. This is due to rendering being offloaded from cpu so we can allocate more cores to the emulator.

We were initially worried about GPU acceleration stability, but we’ve found the GPU-accelerated emulators are actually more stable when compared to the software emulated GL stack of swiftshader. Turning on GPU-acceleration by default was a no-brainer for us.

In rare cases you may still want use swiftshader, for example when your goal is pixel perfect screenshots between various platforms. In that case, you can always switch to swiftshader by:

  • adding gpu=software in ew-cli or GitHub Actions
  • specifying GpuMode.SOFTWARE when using the Gradle plugin

Default device is now model=Pixel7,version=30

The current default of model=Pixel2,version=27 doesn’t really reflect a common Android device form factor any more and API 27 might even be lower than some app’s minSdkVersion. Starting from 1.0.0-rc01 the new default is model=Pixel7,version=30 in ew-cli, the emulator.wtf Gradle plugin and GitHub actions.

This does not mean the Pixel2 or version 27 emulators are unavailable though! You can still go all the way back to api 21 if your tests require it.

Fixed pulled directories containing a double path when downloaded

When asking to pull /sdcard/Download/foo directory (with a file bar.txt), the local pulled file had a path of sdcard/Download/foo/foo/bar.txt (note the double-foo). This has been fixed in 1.0 and the path is now sdcard/Download/foo/bar.txt as expected. This sounds like a small thing, but we didn’t want to break your workflow.

Using GITHUB_TOKEN in place of emulator.wtf api token is no longer supported

Using the GITHUB_TOKEN has always been a bit of a hack and is going away. Fret not, however, as there’s a good replacement available - emulator.wtf now supports using GitHub OIDC tokens for authentication. It has all the same benefits that the old tokenless invoke had - you don’t need to specify any API tokens or have to worry about rotation. As an added bonus you can control access on a per-repository basis, give access to multiple GitHub organizations at the same time and you don’t need to install the emulator.wtf GitHub app for OIDC tokens to work.

Read more about OIDC authentication here.

Slight changes parsing --test-targets and --test-targets-for-shard arguments

We’ve made a few improvements to how --test-targets and --test-targets-for-shard behave. Most notably using whitespace as a statement separator no longer works.

# old
--test-targets "class com.example.One annotation com.example.MyAnnotation"
# new
--test-targets "class com.example.One,annotation com.example.MyAnnotation"

We’ve also merged the parsing rules between --test-targets and --test-targets-for-shard so they’ll behave exactly the same. We, uh, have no excuse why they didn’t in the first place :).

There are also new test target types you can specify, we finally added support for both regex and testFile. Note that testFile uses the local path of your machine / CI job.

Gradle plugin now requires Java 17 and Android Gradle Plugin 8.1.0 or later

We’re dropping support for Android Gradle Plugin (AGP) versions 8.x or older to simplify delivering new features in the emulator.wtf Gradle Plugin. As AGP 8.1.x requires Java 17 so do we.

Gradle plugin uses type-safe APIs instead of maps

The Gradle plugin has seen some DSL improvements and we’ve swapped some Map / String types for fully typed equivalents for easier use and IDE autocompletion.

here’s a device {} DSL for specifying devices, which looks much clearer when using Gradle scripts in Kotlin:

// old
devices = listOf(
  mapOf(
    "model" to "NexusLowResAtd",
    "version" to "30"
  ),
  mapOf(
    "model" to "Pixel2",
    "version" to "23"
  )
)

// new
device {
  model = DeviceModel.NEXUS_LOW_RES_ATD
  version = 30
}

device {
  model = DeviceModel.PIXEL_2
  version = 23
}

Test-targets have gotten a similar makeover:

// old
testTargets = "class foo.bar.Baz"
// new
targets {
  testClass("foo.bar.Baz")
}

And last, but not least, flakyTestRepeatMode now takes a proper enum:

// Whether to reattempt full shards (ALL) or only failed tests (FAILED_ONLY)
// in case of test failures. Defaults to FAILED_ONLY.
flakyTestRepeatMode = FlakyRepeatMode.FAILED_ONLY

Gradle plugin automatically adds the emulator.wtf test-runtime-android dependency

The emulator.wtf Gradle plugin now automatically adds the test-runtime-android dependency. This will give you per-test video captures by default.

If, for some reason, you want to disable this behaviour you can control this behaviour with the wtf.emulator.addruntimedependency=false Gradle property in your project or global gradle.properties file.

All emulator.wtf GitHub actions now use the Node 24 runtime

The GitHub actions have been updated to use the Node 24 runtime. This should only affect you if you’re using self-hosted runners, in which case make sure your GHA runner is upgraded to v2.327.1 or later.

New features

Gradle-managed devices support in the emulator.wtf Gradle plugin

The gradle plugin 1.0.0-rc01 and later support backing Gradle-managed devices with emulator.wtf emulators. This is a great way to quickly plug emulator.wtf into your existing GMD-based test infrastructure. When running tests, the configuration options from the emulatorwtf {} block are still picked up.

Example of registering a Gradle-managed device backed by emulator.wtf:

import wtf.emulator.ewDevices
import wtf.emulator.DeviceModel

android {
  testOptions.managedDevices.ewDevices {
    register("ewPixel7api33") {
      device = DeviceModel.PIXEL_7
      apiLevel = 33
    }
  }
}

Baseline profiles in the emulator.wtf Gradle plugin

Using Gradle-managed devices opens up the door to generate baseline profiles with emulator.wtf emulators. This way you can improve your CI job times and stability by eliminating any use of local emulators altogether.

Expanding on the GMD example above, you can register emulator.wtf devices for use with the baseline plugin:

import wtf.emulator.ewDevices
import wtf.emulator.DeviceModel

android {
  testOptions.managedDevices.ewDevices {
    register("ewPixel7api33") {
      device = DeviceModel.PIXEL_7
      apiLevel = 33
    }
  }
}

baselineProfile {
  managedDevices += "ewPixel7api33"
  useConnectedDevices = false
}

ew-cli improvements

A few smaller ew-cli improvements coming with 1.0 and worth a separate mention:

When using ew-cli in machine-readable --json mode with other tooling you get a new runResultsSummary field in the output JSON object. The field contains useful things for quick CI reporting like test counts and first encountered failure details (test method, stacktrace, etc).

You can finally customize the separator in comma-separated value arguments in ew-cli by prefixing the value with ^SEP^, e.g. ^;^ to use the ; as a separator instead of the default ,.

For example if you wanted to pass in foo=ab,cd and bar=baz as test runner arguments you finally can! See the example below.

ew-cli --environment-variables '^;^foo=ab,cd;bar=baz' ...

Other recent improvements

Some recent improvements from the past few years which you might’ve missed if you’ve been with us for a while:

  • Develocity reporting from the Gradle plugin. If you use Develocity you can wire up reporting emulator.wtf test results to Develocity by adding testReporters = [TestReporter.DEVELOCITY] to your emulatorwtf {} configuration block in Gradle
  • Targeted runtime sharding - the easiest way to manage sharding within your tests. You indicate the target runtime and emulator.wtf will figure out both how many shards are needed and assign tests to those shards based on historical runtime. Read more about targeted runtime sharding here.
  • Per-test videos - by including the test-runtime-android dependency you can get frame-perfect video captures of individual tests.

Pinning to a specific emulator.wtf version

The ew-cli script always fetches the latest available version before running your tests. This is great for ad-hoc command-line usage but not so great for running tests in CI, a breaking change on our side could start failing your builds out of the blue, a bad day for everyone.

Similarly, you might want to try out release candidate versions or hold off on upgrading to 1.x until you’ve dealt with the breaking changes above (although we really recommend updating!).

Here’s how you can pin emulator.wtf to a specific version:

  • CLI: use the EW_VERSION environment variable to pin to a specific version.

    You can set EW_VERSION=1.0.0-rc01 to try out the release candidate: EW_VERSION=1.0.0-rc01 ew-cli --app ... --test ...

  • Gradle: the wtf.emulator.gradle plugin always uses a fixed CLI version, no need to do anything. To try out 1.0.0-rc01 with the breaking changes, upgrade the Gradle plugin to 1.0.0-rc01 or later.

  • GitHub Actions: if you’re using any of our GitHub Actions then you can either depend on a major version tag like v0 or a specific exact version like v0.9.10. The specific versions will use a fixed ew-cli version to run the tests. For trying out the release candidate, use the v1-rc tag.

What’s cooking?

Last but not least there are two bigger features coming that are not fully baked yet for inclusion in the 1.0 release.

Emulator sessions is a completely new mode of operation where instead of running tests you can use ew-cli to fire up an emulator and get a direct adb connection to it. It works with tools like scrcpy as well so you can get a live interactable version of our emulators. This is great to debug tests locally, but we have more interesting usecases in mind. Stay tuned!

Egress tunnel is a networking mode where any egress traffic from the emulator is diverted to the machine running ew-cli, whether it’s your local development computer or the container your CI job is running in. It’s a great way to expose backend test infrastructure for larger tests without having to punch holes to firewalls.

Both of these features are in closed alpha today! Give us a ping at hello@emulator.wtf if you’d like to give them a go.