Review and Stretch Goals

5 min read

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.txt complete
  • pytest.ini has markers = section with all used markers registered
  • .env file present and gitignored; .env.example committed for teammates
  • No credentials hardcoded anywhere

Fixtures and architecture

  • driver fixture uses function scope (new session per test)
  • pytest_runtest_makereport hook present in conftest.py
  • Screenshot saved to screenshots/ on test failure
  • Page objects inherit from BasePage
  • No find_element calls in test files — only page object methods

Locators and waits

  • All locators are ACCESSIBILITY_ID or UIAutomator/predicate strings
  • No time.sleep() anywhere in the codebase
  • wait_for_visible / wait_for_clickable used in page objects, not raw find_element
  • hide_keyboard() called before tapping Login button

Test design

  • @pytest.mark.smoke on 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-pytest installed and --alluredir used
  • @allure.epic, @allure.feature, @allure.story present 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: true in 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 reference

Let 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.

// tip to track lessons you complete and pick up where you left off across devices.