The capstone is complete when all tests run reliably, screenshots appear on failure, Allure shows structured results, and CI runs the smoke suite on every push. Use this checklist to verify before moving on.
Self-review checklist
Project setup
- Virtual environment activated,
requirements.txtcomplete -
pytest.inihasmarkers =section with all used markers registered -
.envfile present and gitignored;.env.examplecommitted for teammates - No credentials hardcoded anywhere
Fixtures and architecture
-
driverfixture uses function scope (new session per test) -
pytest_runtest_makereporthook present inconftest.py - Screenshot saved to
screenshots/on test failure - Page objects inherit from
BasePage - No
find_elementcalls in test files — only page object methods
Locators and waits
- All locators are
ACCESSIBILITY_IDor UIAutomator/predicate strings - No
time.sleep()anywhere in the codebase -
wait_for_visible/wait_for_clickableused in page objects, not rawfind_element -
hide_keyboard()called before tapping Login button
Test design
-
@pytest.mark.smokeon the standard user login test -
@pytest.mark.no_retry(or equivalent) on checkout tests - Parametrized test IDs are human-readable (not default indices)
- Assertions include descriptive messages:
assert x, "description"
Reporting
-
allure-pytestinstalled and--alluredirused -
@allure.epic,@allure.feature,@allure.storypresent on tests - Allure report shows screenshot attachment for any failed test
- Environment properties file written (platform, device, Appium version)
CI
- GitHub Actions workflow runs smoke suite on PR
- Allure results uploaded as artifact
-
disable-animations: truein the emulator runner action
Common final mistakes
time.sleep() sneaking in: Search the entire codebase:
grep -r "time.sleep" tests/ pages/ utils/Any result should be investigated and replaced with an explicit wait.
Missing hide_keyboard() before taps: On Android, the soft keyboard covers the lower third of the screen. Any button in that area fails with ElementClickInterceptedException. Always call hide_keyboard() after the last send_keys().
Sharing driver between tests via class-level attribute: A common mistake when using test classes:
class TestLogin:
driver = None # WRONG — shared state between tests
def test_one(self, driver):
TestLogin.driver = driver # WRONG — stores referenceLet pytest inject the driver fixture into each test method independently. Never store it as a class attribute.
allure-pytest not generating steps from @allure.step: The @allure.step decorator on page object methods only works when the methods are called during a test. If you put @allure.step on a function called from a fixture (not from a test body), the step won't appear in the report. Move complex setup logic into helper methods called from the test body, or into allure.step context managers in the test.
What comes next
XCUITest for iOS-native testing
Appium is excellent for cross-platform coverage, but XCUITest gives you access to tools Appium can't reach: Swift XCTAttachment for high-quality screenshots, XCTMetrics for precise CPU/memory measurements, and Xcode's UI test recording. If your product is iOS-first, learn XCUITest for the scenarios where native precision matters.
Espresso for Android in-process testing
Espresso runs tests in the same process as the Android app — no Appium server, no HTTP round-trips, automatic main-thread synchronisation. Espresso tests run 5–10× faster than Appium and can access private app state. Use it for UI unit tests at the component level while keeping Appium for end-to-end coverage.
Device farms at scale
The BrowserStack setup from Chapter 6 demonstrates 2 devices. For 20+ devices, you need a proper sharding strategy: split tests by marker or file, run each shard on a dedicated device in parallel, and merge Allure results after all shards complete. Look into BrowserStack's App Automate API for dynamic session allocation.
Mobile performance testing
Functional coverage is the foundation. The next layer is performance: cold start time, frame rate during scroll, memory usage under load. Python has bindings for Android's dumpsys and iOS's instruments that let you add performance assertions to existing test sessions.
Accessibility scanning
driver.execute_script("mobile: accessibilityAudit") on iOS returns a list of WCAG violations for the current screen. Android's Accessibility Test Framework can scan via driver.execute_script. Add these scans to BasePage.__init__() or as a separate @pytest.fixture(autouse=True) that runs after each navigation.
The mobile testing roadmap path covers all of these topics — the Appium Python foundation you've built here is the prerequisite for every specialisation on that path.