A production-grade, open-source automation framework that makes mobile app testing significantly easier, faster, and more reliable.
Features β’ Quick Start β’ Documentation β’ Examples β’ Contributing
Sapphire is a modern, production-ready mobile test automation framework built on top of Appium 2.x for iOS and Android testing. It transforms complex Appium commands into simple, intuitive APIs that focus on what you want to test, not how to write XPath.
β¨ Easy - Find elements by labels, not complex XPaths π Fast - Smart waits and retries eliminate flaky tests π Reliable - Comprehensive logging, screenshots, and detailed reports π― Production-Ready - Battle-tested patterns and enterprise-grade quality π Cross-Platform - Single API for both iOS and Android π§ Extensible - Clean architecture with clear extension points
// β Traditional Appium - Too much boilerplate
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
By locator = By.xpath("//*[@label='Submit' or @text='Submit' or @name='Submit']");
WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
if (element != null && element.isDisplayed()) {
element.click();
}// β
Sapphire - Clean and intuitive
executor.clickOnElementWithLabel("Submit");| Traditional Appium | Sapphire |
|---|---|
| 6+ lines of code | 1 line of code |
| Platform-specific XPaths | Cross-platform labels |
| Manual wait management | Automatic smart waits |
| Complex error handling | Built-in retry logic |
| Basic logging | Rich reporting with screenshots |
Find elements by visible text - works across iOS and Android without platform-specific code:
executor.findElementWithLabel("Sign In");
executor.findElementContainsLabel("Sign"); // Partial matchAutomatic intelligent waits with configurable timeouts:
executor.findElementWithLabel(15, "Slow Element"); // Wait up to 15 seconds
executor.performScrollAndFindElementWithLabel("Hidden Element"); // Scroll until foundBuilt-in support for all common mobile gestures:
executor.performScrollToBottom(3); // Scroll 3 pages down
executor.performScrollLeftToRight(); // Swipe horizontally
executor.performScroll(x1, y1, x2, y2); // Custom gesturesAutomatic screenshot capture when tests fail:
// Configured in properties
screenshot.on.failure=trueBeautiful HTML reports with screenshots, logs, and metrics:
executor.extentReporter.startReportingForTest("Login Test");
executor.extentReporter.trackReport(LogStatus.PASS, "Login successful");| Platform | Support | Versions |
|---|---|---|
| β Android | Native, Hybrid, Mobile Web | 7.0+ |
| β iOS | Native, Hybrid, Mobile Web | 12.0+ |
| β Real Devices | Local & Remote | All |
| β Simulators/Emulators | Full Support | All |
| β Cloud Providers | Sauce Labs, BrowserStack, AWS Device Farm | All |
- Page Object Model Support - Built-in base classes and patterns
- Data-Driven Testing - Excel and JSON data providers
- Parallel Execution - Run tests concurrently for faster feedback
- Headless Execution - CI/CD ready with headless emulator support
- Video Recording - Optional test execution recording
- Configuration Management - Type-safe configuration with external properties
- Logging Framework - SLF4J + Logback for structured logging
- Allure Integration - Beautiful test reports with Allure
# Java 17 or higher
java -version
# Maven 3.8+
mvn -version
# Appium 2.x
npm install -g appium@next
appium driver install uiautomator2 # For Android
appium driver install xcuitest # For iOS
# Start Appium Server
appiumAdd Sapphire to your pom.xml:
<dependency>
<groupId>com.evig.sapphire</groupId>
<artifactId>sapphire</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>import com.evig.sapphire.GenericExecutor;
import com.evig.sapphire.provider.DriverProvider;
import org.testng.annotations.*;
public class MyFirstTest {
GenericExecutor executor;
@BeforeMethod
public void setup() throws Exception {
// Configure capabilities
DriverProvider.CapabilitiesBuilder builder =
new DriverProvider.CapabilitiesBuilder(
"/path/to/your/app.apk", // Your app path
"android", // Platform: android or ios
"13", // OS version
"emulator-5554" // Device name
);
executor = new GenericExecutor(builder.build());
}
@Test
public void myFirstTest() {
// Find and click "Sign In"
executor.clickOnElementWithLabel("Sign In");
// Enter credentials
executor.setTextInElementsWithLabel("test@example.com", "Email");
executor.setTextInElementsWithLabel("password123", "Password");
// Submit
executor.clickOnElementWithLabel("Sign In");
// Verify success
assert executor.isElementsDisplayedWithLabel("Welcome");
}
@AfterMethod
public void teardown() {
executor.quitDriver();
}
}Run your test:
mvn test -Dtest=MyFirstTestπ That's it! Your first test is running!
| Guide | Description |
|---|---|
| Quick Start | Get started in 5 minutes |
| Testing Guide | Comprehensive testing guide with headless & CI/CD |
| Architecture | Framework architecture and design |
| Migration Guide | Upgrade from 1.x to 2.0 |
// By label (recommended - cross-platform)
executor.findElementWithLabel("Submit");
executor.findElementContainsLabel("Sub"); // Partial match
// Case-insensitive search
executor.findElementWithLabel("submit", true);
// With custom timeout
executor.findElementWithLabel(20, "Submit"); // Wait up to 20 seconds
// By XPath (when needed)
executor.findElementByXpath("//button[@id='submit']");
// Scroll to find
WebElement el = executor.performScrollAndFindElementWithLabel("Hidden Element");// Click
executor.clickOnElementWithLabel("Button");
executor.clickOnElement(webElement);
// Text input
executor.setTextInElementsWithLabel("John Doe", "Name");
executor.clearElementText(webElement);
// Validation
boolean displayed = executor.isElementsDisplayedWithLabel("Success");
boolean textMatches = executor.validateElementText("Expected", element);// Vertical scrolling
executor.performScrollToBottom(); // Scroll to bottom
executor.performScrollToBottom(3); // Scroll 3 pages down
executor.performScrollToTop(); // Scroll to top
// Horizontal scrolling
executor.performScrollLeftToRight();
executor.performScrollRightToLeft();
// Custom gestures
executor.performScroll(fromX, fromY, toX, toY);// Create config.properties
appium.server.url=http://localhost:4723
implicit.wait=10
screenshot.on.failure=true
headless=false
// Use type-safe configuration
SapphireConfig config = ConfigFactory.create(SapphireConfig.class);
String url = config.appiumServerUrl();// Start test reporting
executor.extentReporter.startReportingForTest("Login Test");
// Log test steps
executor.extentReporter.trackReport(LogStatus.PASS, "User logged in successfully");
// Stop reporting
executor.extentReporter.stopReportingForTest();
// Reports generated at: report/[platform]/ExtentReport.htmlpublic class LoginTest {
GenericExecutor executor;
@BeforeMethod
public void setup() throws Exception {
DriverProvider.CapabilitiesBuilder builder =
new DriverProvider.CapabilitiesBuilder(
System.getenv("APP_PATH"),
"android",
"13",
"emulator-5554"
)
.setWaitTimeInSeconds(10)
.setExtraCapability("noReset", "true")
.setExtraCapability("autoGrantPermissions", "true");
executor = new GenericExecutor(builder.build());
executor.extentReporter.startReportingForTest("Login Test");
}
@Test
public void testSuccessfulLogin() {
// Verify app launched
assert executor.validateAppLaunchWithElements("Sign In", "Join");
// Navigate to login
executor.clickOnElementWithLabel("Sign In");
// Enter credentials
executor.setTextInElementsWithLabel("user@example.com", "Email");
executor.setTextInElementsWithLabel("SecurePass123", "Password");
// Submit
executor.clickOnElementWithLabel("Sign In");
executor.waitForSeconds(2);
// Verify success
assert executor.isElementsDisplayedWithLabel("Welcome", "Dashboard");
executor.extentReporter.trackReport(LogStatus.PASS, "Login successful");
}
@AfterMethod
public void teardown() {
executor.extentReporter.stopReportingForTest();
executor.quitDriver();
}
}// LoginPage.java
public class LoginPage {
private final GenericExecutor executor;
public LoginPage(GenericExecutor executor) {
this.executor = executor;
}
public void enterEmail(String email) {
executor.setTextInElementsWithLabel(email, "Email");
}
public void enterPassword(String password) {
executor.setTextInElementsWithLabel(password, "Password");
}
public void clickSignIn() {
executor.clickOnElementWithLabel("Sign In");
}
public boolean isWelcomeDisplayed() {
return executor.isElementsDisplayedWithLabel("Welcome");
}
}
// Test using Page Object
@Test
public void testLoginWithPageObject() {
LoginPage loginPage = new LoginPage(executor);
loginPage.enterEmail("user@example.com");
loginPage.enterPassword("password");
loginPage.clickSignIn();
assert loginPage.isWelcomeDisplayed();
}@DataProvider
public Object[][] loginData() {
return TestDataProvider.provideDataFromExcelFile("login_data.xlsx");
}
@Test(dataProvider = "loginData")
public void testMultipleLogins(HashMap<String, String> data) {
String email = data.get("email");
String password = data.get("password");
executor.clickOnElementWithLabel("Sign In");
executor.setTextInElementsWithLabel(email, "Email");
executor.setTextInElementsWithLabel(password, "Password");
executor.clickOnElementWithLabel("Sign In");
assert executor.isElementsDisplayedWithLabel("Welcome");
}// HeadlessTest.java
SapphireConfig config = ConfigFactory.create(SapphireConfig.class);
DriverProvider.CapabilitiesBuilder builder =
new DriverProvider.CapabilitiesBuilder(app, platform, version, device)
.setExtraCapability("isHeadless", String.valueOf(config.headless()))
.setExtraCapability("avdArgs", "-no-window -no-audio");
// Run headless
mvn test -Dheadless=trueSee examples/ for more complete examples.
Sapphire follows a clean, layered architecture:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Test Layer (Your Tests) β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Execution Layer (GenericExecutor) β
β β’ Element interactions β’ Validation β’ Gestures β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Inspection Layer (ElementInspector) β
β β’ Element finding β’ Waits β’ Locator strategies β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Driver Layer (DriverProvider) β
β β’ Session management β’ Capabilities β’ Configuration β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Appium / Selenium β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Design Principles:
- Separation of Concerns - Each layer has a clear responsibility
- Extensibility - Easy to extend with custom functionality
- Cross-Platform - Single API for iOS and Android
- Testability - Framework code is testable with unit tests
Read more in Architecture Documentation.
# All tests
mvn test
# Specific platform
mvn test -Pandroid
mvn test -Pios
# Specific test
mvn test -Dtest=LoginTest
# Headless mode
mvn test -Dheadless=true
# With coverage
mvn test jacoco:report# Start complete test environment (Appium + Emulator + Tests)
docker-compose up
# Run tests in container
docker-compose up sapphire-tests
# View logs
docker-compose logs -f
# Clean up
docker-compose down -vSapphire includes pre-configured GitHub Actions:
# .github/workflows/ci.yml
- Builds on every push
- Runs tests on PR
- Multi-OS (Ubuntu, macOS)
- Multi-Java (17, 21)
- Security scanning
- Code quality checksSee Testing Guide for comprehensive testing documentation.
| Platform | Min Version | Tested Versions |
|---|---|---|
| Android | 7.0 (API 24) | 7.0, 9.0, 11, 12, 13, 14 |
| iOS | 12.0 | 12.0, 13.0, 14.0, 15.0, 16.0, 17.0 |
| Appium | 2.0+ | 2.0, 2.5 |
| Java | 17+ | 17, 21 |
Create config.properties:
# Appium Server
appium.server.url=http://localhost:4723
appium.command.timeout=60
# Waits (seconds)
implicit.wait=10
explicit.wait=30
page.load.timeout=30
# Retry
retry.count=3
retry.delay=1000
# Screenshots & Recording
screenshot.on.failure=true
screenshot.directory=./screenshots
video.recording=false
video.directory=./videos
# Reporting
report.directory=./report
log.level=INFO
# Headless
headless=falseOverride with system properties:
mvn test -Dappium.server.url=http://remote:4723 -Dheadless=trueSapphire generates comprehensive test reports:
Beautiful HTML reports with screenshots, logs, and metrics.
Location: report/[platform]/ExtentReport.html
Features:
- Test execution timeline
- Pass/Fail statistics
- Screenshots on failure
- Detailed logs
- Environment info
# Generate Allure report
mvn test
allure serve target/allure-resultsDefault TestNG HTML reports.
Location: target/surefire-reports/index.html
We welcome contributions! Sapphire is open-source and community-driven.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow Google Java Style Guide
- Write tests for new features
- Update documentation
- Ensure CI passes
Read our Contributing Guide for detailed guidelines.
We follow the Contributor Covenant Code of Conduct.
Contributors are recognized in:
- README.md (Contributors section)
- Release notes
- GitHub contributors page
Security is a top priority. We:
β Maintain zero known vulnerabilities β Regularly update dependencies β Scan for security issues in CI/CD β Follow secure coding practices
Report Security Issues: See SECURITY.md
Sapphire is licensed under the MIT License.
MIT License
Copyright (c) 2025 Sapphire Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...
You are free to:
- β Use commercially
- β Modify
- β Distribute
- β Use privately
Sapphire is built on top of excellent open-source projects:
- Appium - Mobile automation framework
- Selenium - Web automation standard
- TestNG - Testing framework
- ExtentReports - Test reporting
- Allure - Test reporting
- Logback - Logging framework
Special thanks to all contributors!
- π Documentation: Wiki
- π¬ Discussions: GitHub Discussions
- π Issues: Bug Reports
- π§ Contact: Create an issue or discussion
- β Star this repository
- π Watch for releases
- π Subscribe to discussions
- β Appium 2.x support
- β Java 17 support
- β Modern logging (SLF4J + Logback)
- β ExtentReports 5.x
- β Allure integration
- β Headless execution
- β Docker support
- β CI/CD ready
- Enhanced fluent API
- Improved error messages
- More gesture patterns
- Performance metrics
- Playwright integration (for web testing)
- Visual regression testing
- AI-powered element detection
- Performance testing capabilities
- iOS/Android inspector tool
See CHANGELOG.md for version history.
- π Faster Development - Write tests 5x faster with intuitive APIs
- π― Better Reliability - Smart waits eliminate flaky tests
- π Easier Debugging - Rich logging and screenshots
- π Great Documentation - Comprehensive guides and examples
- π° Zero Cost - Completely free and open-source
- π‘οΈ Enterprise Quality - Production-grade code and architecture
- π€ Community Support - Active community and regular updates
- π§ Customizable - Extend and adapt to your needs
- β Proven - Used in production by multiple teams
- π Secure - Zero known vulnerabilities
- π Maintainable - Clean code, well-documented
- π Cross-Platform - Single framework for iOS and Android
If Sapphire helps you, please star the repository!
Made with β€οΈ by the Sapphire Community