You have built a complete Appium test suite: a Maven project, Page Object Model, cross-platform tests, context switching, parallel execution, and a CI workflow. This final lesson reviews the key decisions made in the capstone, flags common mistakes to watch for in production, and gives you stretch goals to push your skills further.
What you built and why it holds up
Thread-local driver in BaseTest. Each test method creates and destroys its own AppiumDriver. This is more expensive than sharing a driver across a test class (@BeforeClass), but it eliminates state leakage between tests entirely. In a parallel suite, it is also the only safe option. The cost is longer total run time for sequential runs; the benefit is complete test independence.
DriverFactory with a platform switch. All Appium configuration lives in one class. When you need to target a cloud provider, add one branch to DriverFactory. Nothing in test code or page objects changes.
Page objects return the next page. loginPage.tapLogin() returns a DashboardPage. This models the actual navigation and prevents test code from instantiating the wrong page after an action. If tapLogin() lands on an error dialog instead, return this from tapLoginExpectingError() to keep the test's page reference correct.
WaitUtils wraps WebDriverWait. A WebDriverWait with Duration.ofSeconds(15) in every page class would repeat 15 everywhere and make timeout tuning a find-and-replace exercise. Centralising it means you change the timeout once.
Context switching in the constructor of StatementsPage. By switching to the WebView context in StatementsPage's constructor, every method in that class works in web context without each method calling switchToWebView(). The returnToNative() method explicitly hands back control to the caller, which mirrors how navigation works: a page class owns its context.
Common mistakes in production suites
Thread.sleep() in tests. It is the most common fix for flaky tests and the worst one. Sleep-based waits fail on slow CI machines and waste time on fast ones. The correct fix is always an explicit wait on a specific condition: element visible, text present, URL changed.
Implicit wait set too high. implicitlyWait(30) means every findElements that returns empty waits 30 seconds. On a suite of 200 tests, that adds up. Keep implicit wait at 0 and use explicit waits for everything.
Hardcoded coordinates. Coordinates like swipe(540, 1400, 540, 400) are device-specific. A test written on a Pixel 6 screen will fail on a Pixel 4 with a different resolution. Always calculate coordinates as percentages of driver.manage().window().getSize().
Missing driver.quit() on failure. If @AfterMethod calls driver.quit() unconditionally, orphaned sessions are not possible. If the quit is in a try block without a finally, a crash in @AfterMethod before quit() leaves the session open. Use try-finally:
@AfterMethod
public void tearDown() {
AppiumDriver driver = driverThread.get();
try {
if (!result.isSuccess()) saveScreenshot(driver, result.getName());
} finally {
if (driver != null) driver.quit();
driverThread.remove();
}
}Locators embedded in test code. A findElement call in a test class is a page object waiting to be extracted. Every locator in a test method is one refactor away from breaking three other test methods that copy it.
Stretch goal 1: Allure reporting
Add Allure reporting for rich HTML test reports with screenshots attached to failures:
<!-- pom.xml -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.27.0</version>
</dependency>Attach screenshots in @AfterMethod:
@Attachment(value = "Failure screenshot", type = "image/png")
public byte[] saveScreenshot() {
return driver.getScreenshotAs(OutputType.BYTES);
}Generate the report:
mvn test
allure serve target/allure-resultsStretch goal 2: BrowserStack integration
Move the smoke suite to run against real devices on BrowserStack:
- Add a
browserstackcase toDriverFactorythat readsBS_USERNAMEandBS_ACCESS_KEYfrom environment variables - Add
bstack:optionswithdeviceName,osVersion,projectName, andbuildName - Update the GitHub Actions workflow to inject the secrets and add
-Dtarget=browserstackto the Maven command - Run and verify the session video in the BrowserStack dashboard
Stretch goal 3: Appium Plugin — Execute Driver
The Appium Execute Driver plugin allows sending multiple commands in a single network round trip using a WebdriverIO script syntax. For suites with many sequential interactions, this can cut 30–40% of total run time by eliminating HTTP round-trip latency.
Install:
appium plugin install execute-driver
appium --use-plugins=execute-driverStretch goal 4: Visual regression with Applitools
Add screenshot comparisons to catch UI regressions that functional assertions miss:
// In BaseTest setUp()
eyes = new Eyes();
eyes.setApiKey(System.getenv("APPLITOOLS_API_KEY"));
eyes.open(driver, "FinanceApp", testName);
// In a test
eyes.checkScreen("Dashboard");
// In tearDown()
eyes.closeAsync();
runner.getAllTestResults(false);Applitools compares screenshots pixel-by-pixel against a baseline and flags layout regressions. It is especially valuable for catching rendering differences between OS versions.
Where to go from here
- Appium documentation at appium.io — the driver plugin documentation is the most up-to-date reference for Appium 2.x APIs
- UIAutomator2 driver docs — covers all
mobile:commands specific to Android - XCUITest driver docs — covers iOS-specific
mobile:commands, WebDriverAgent configuration, and real device setup - Appium Pro (https://appiumpro.com) — an archive of practical Appium technique articles by the original creator
The most important thing after completing this course is writing tests for a real app you care about. Every app has quirks that theory cannot prepare you for: a WebView that only loads under certain network conditions, a permission dialog that appears differently on one OEM skin, a gesture that requires a specific duration to register. Mastery comes from debugging those problems in a real codebase, not from reading about them.
You have the full toolkit. Build something.