Mobile Development 13 min read

Ensuring Consistency in Airbnb's Android Automated Testing Framework

In the sixth Airbnb Android testing article, the team identifies flaky‑test sources such as nondeterministic sharding, asynchronous execution, view and drawable caching, shared‑preference leaks, time drift, and RecyclerView pre‑fetching, and resolves them by clearing shared state, injecting test‑scoped coroutine scopes, forcing view refreshes, mocking time, clearing drawable caches, disabling pre‑fetching, using synchronous drawables, and wrapping WebViews to block network loads while logging details.

Airbnb Technology Team
Airbnb Technology Team
Airbnb Technology Team
Ensuring Consistency in Airbnb's Android Automated Testing Framework

In the sixth article of the Airbnb automated testing framework series, the authors discuss common factors that cause inconsistent test results and describe how they address these issues.

The main challenges include nondeterministic test ordering caused by Flank’s dynamic sharding, asynchronous code execution (RxJava, Kotlin coroutines, AsyncTask, Executor, custom threads), view caching (invalidate(), requestLayout()), shared preferences leakage across simulated states, fluctuating system time/date, drawable caching, memory‑related crashes, delayed runnables, non‑simulated states, image loading, WebView loading, and RecyclerView pre‑fetching.

To mitigate these problems, the team implements several strategies:

Manually clear shared state between test fragments and prevent memory leaks by limiting test shard duration and using LeakCanary.

Provide a custom async‑runner function that can block or detect the completion of asynchronous code, and inject a test‑scoped CoroutineScope into ViewModels.

Force view hierarchy refresh by calling invalidate() and requestLayout() on all views.

Clear SharedPreferences (and other storage) after each simulated state.

Mock time and date using a wrapped JodaTime API so that now() and today() return constant values.

Clear drawable caches after each screenshot by triggering a configuration change with the following code:

AppCompatDrawableManager.get().onConfigurationChanged(view.context)
val resources = view.resources
val currentConfig = resources.configuration
val currentDisplayMetrics = resources.displayMetrics

resources.updateConfiguration(null, currentDisplayMetrics)
resources.updateConfiguration(currentConfig, currentDisplayMetrics)

Enable android:largeHeap and reuse a single bitmap for large screenshots to avoid OOM.

Disable RecyclerView pre‑fetching by calling setItemPrefetchEnabled(false) on all LayoutManagers after the fragment is laid out.

Replace image loading with synchronous local drawables during tests and capture the intended URL in JSON reports.

Wrap WebView in a custom view to block network loading and record URL, user‑agent, and headers in test reports.

The article concludes by summarizing these consistency‑preserving techniques and previewing the next piece, which will cover test code generation and continuous integration.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

automationandroid testingAsync HandlingFlankmemory leaksSharedPreferences
Airbnb Technology Team
Written by

Airbnb Technology Team

Official account of the Airbnb Technology Team, sharing Airbnb's tech innovations and real-world implementations, building a world where home is everywhere through technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.