Skip to content
Peenaka Wedamulla

All case studies

Open Source · Personal R&D

Espresso: Android UI Automation Framework

A pluggable, enterprise-grade Android UI test automation framework built around the Page Object Model. Ships the scaffolding (BrowserStack cloud integration, thread-safe parallel execution, dual reporting, and environment-variable-driven config) so teams focus on writing tests, not wiring infrastructure.

Problem

Every Android test automation project starts the same way: someone has to wire up BrowserStack, write a BaseTest, figure out config loading, connect Allure, and make it all safe to run in parallel before the first real test case can be written. That setup work is expensive, repetitive, and invisible in the final suite, but if it's done poorly, it haunts every test author for the life of the project.

Espresso is that scaffolding, done once and done properly.

Architecture

The framework is structured in three layers:

┌────────────────────────────────────┐
│         Your Test Classes          │
│    extend BaseTest · use Pages     │
├────────────┬───────────┬───────────┤
│ Page Objects│ Test Data │ Assertions│
│ (POM)      │ (JSON)    │ (Truth)   │
├────────────┴───────────┴───────────┤
│      EspressoActions interface     │
│  Default stub · Your real impl     │
├────────────────────────────────────┤
│ Config · BrowserStack · Reporting  │
└────────────────────────────────────┘

EspressoActions is an interface: the single most important architectural decision. You provide the real Espresso implementation for your app; the default logging stub lets you validate the framework wiring in CI without a device attached. This makes the framework testable in isolation and removes the device as a dependency for build-system verification.

Constructor-injected page objects: pages receive an EspressoActions instance, so there is no static coupling and pages are trivially mockable in unit tests.

Classpath-based config and data: ConfigManager loads config.json from the classpath, not a filesystem path, so it behaves identically on a developer machine and in CI. ${ENV_VAR} placeholders in the config are resolved at runtime, with no secrets committed to source.

ThreadLocal session state: BaseTest stores the current driver and actions instance per thread, making the suite safe for TestNG parallel execution without locks or shared mutable state.

Key components

  • BaseTest: test lifecycle (setup / teardown), BrowserStack session management, screenshot capture on failure, and the hook for injecting your EspressoActions implementation.
  • ConfigManager: reads config.json, resolves ${VAR} against environment variables, and exposes typed accessors. Fails fast with a clear message if a required variable is missing.
  • TestDataReader: thread-safe JSON test data loader. Each test gets its own data snapshot; no cross-thread contamination in parallel runs.
  • EnhancedTestListener: TestNG listener wiring Extent Reports. Pass / fail / skip status, timestamps, and attached screenshots with no boilerplate in test code.
  • DeviceCapabilitiesManager: builds BrowserStack desired capabilities from config, keeping capability logic out of test classes.

CI pipeline

The framework ships with a GitHub Actions workflow that:

  • Builds with both Maven and Gradle in the same run.
  • Tests across Java 17 and Java 21 in a matrix.
  • Uploads Allure results as artifacts on every push.

This means the framework validates its own packaging assumptions on every commit, not just on developer machines.

Why it matters for production automation

Most automation frameworks grow organically: config loading gets bolted on, then reporting, then parallel support, each time by a different person. The accumulated inconsistency slows every new team member. Espresso makes the boring parts non-negotiable: parallel-safe from day one, secrets out of code from day one, dual reporting from day one. The team inherits those guarantees without understanding the implementation.