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 yourEspressoActionsimplementation.ConfigManager: readsconfig.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.