Skip to main content

Reusable Appium 2.x + pytest mobile test framework

Project description

appium-pytest-kit

Buy Me a Coffee

appium-pytest-kit is a reusable Appium 2.x + pytest framework library for Python 3.11+.

  • pip install appium-pytest-kit (or install from GitHub — see below)
  • appium-pytest-kit-init to bootstrap configuration, or --framework to scaffold a full project
  • Write tests immediately with built-in fixtures and zero boilerplate

Full documentation: DOCUMENTATION.md


Installation

From PyPI (once published)

pip install appium-pytest-kit

From GitHub

# latest main branch
pip install git+https://github.com/gianlucasoare/appium-pytest-kit.git

# specific branch
pip install git+https://github.com/gianlucasoare/appium-pytest-kit.git@main

# specific tag
pip install git+https://github.com/gianlucasoare/appium-pytest-kit.git@v0.1.0

Local clone (editable, for development)

git clone https://github.com/gianlucasoare/appium-pytest-kit.git
cd appium-pytest-kit
pip install -e ".[dev]"

Quickstart

python -m venv .venv
source .venv/bin/activate
pip install git+https://github.com/gianlucasoare/appium-pytest-kit.git
appium-pytest-kit-init          # creates .env with starter config
# or scaffold a full project:
appium-pytest-kit-init --framework --root my-project
pytest -q

Edit .env with your device and app details, then write tests.


Step-by-step: test a real app in 5 minutes

This example tests the Android Calculator on an emulator. See DOCUMENTATION.md for the full iOS walkthrough and all options.

1. Start Appium and an emulator

appium &
emulator -avd Pixel_7_API_33 &
adb devices    # confirm emulator-5554 is listed

2. Configure .env

APP_PLATFORM=android
APP_APPIUM_URL=http://127.0.0.1:4723
APP_DEVICE_NAME=emulator-5554
APP_PLATFORM_VERSION=13
APP_APP_PACKAGE=com.google.android.calculator
APP_APP_ACTIVITY=com.android.calculator2.Calculator
APP_NO_RESET=true

3. Write a test

# tests/test_calculator.py
import pytest
from appium.webdriver.common.appiumby import AppiumBy

BTN_2      = (AppiumBy.ACCESSIBILITY_ID, "2")
BTN_PLUS   = (AppiumBy.ACCESSIBILITY_ID, "plus")
BTN_3      = (AppiumBy.ACCESSIBILITY_ID, "3")
BTN_EQUALS = (AppiumBy.ACCESSIBILITY_ID, "equals")
RESULT     = (AppiumBy.RESOURCE_ID, "com.google.android.calculator:id/result_final")


@pytest.mark.integration
def test_addition(actions):
    actions.tap(BTN_2)
    actions.tap(BTN_PLUS)
    actions.tap(BTN_3)
    actions.tap(BTN_EQUALS)
    assert actions.text(RESULT) == "5"

4. Run it

pytest -m integration -v

Built-in fixtures

Fixture Scope Description
settings session Resolved AppiumPytestKitSettings — access any config field
appium_server session Server URL and whether it is framework-managed
driver function Live appium.webdriver.Remote, quit automatically after each test
waiter function Explicit waits with WaitTimeoutError on timeout
actions function High-level UI helpers: tap, type_text, text, exists, swipe, and more

Session modes

Control driver lifecycle per test or across the whole session:

APP_SESSION_MODE=clean          # default: fresh driver per test
APP_SESSION_MODE=clean-session  # shared driver, app reset between tests
APP_SESSION_MODE=debug          # shared driver, no reset (fast local debugging)

Device resolution (3-tier priority)

  1. ExplicitAPP_DEVICE_NAME / APP_UDID set in .env or CLI
  2. ProfileAPP_DEVICE_PROFILE=pixel7 resolved from data/devices.yaml
  3. Auto-detectadb devices (Android) or xcrun simctl / xctrace (iOS)
# Use a named profile from data/devices.yaml
pytest --app-device-profile pixel7

# Auto-detect (no device settings needed)
pytest

Failure diagnostics

On test failure the framework automatically captures:

  • Screenshotartifacts/screenshots/<test_id>.png
  • Page sourceartifacts/pagesource/<test_id>.xml
  • Video (if policy allows) → artifacts/videos/<test_id>.mp4
APP_VIDEO_POLICY=never    # default
APP_VIDEO_POLICY=failed   # record and save only on failure
APP_VIDEO_POLICY=always   # record every test
APP_ARTIFACTS_DIR=artifacts

Allure attachments are added automatically when allure-pytest is installed.


Configuration

Settings are loaded from .env → environment variables → CLI flags (highest wins).

pytest --app-platform ios
pytest --app-device-name "Pixel 7" --app-platform-version 13
pytest --appium-url http://192.168.1.10:4723
pytest --app-app-package com.example.app --app-app-activity .MainActivity
pytest --app-session-mode clean-session
pytest --app-device-profile pixel7
pytest --app-video-policy failed
pytest --app-is-simulator
pytest --app-capabilities-json '{"autoGrantPermissions": true}'
pytest --app-manage-appium-server    # start Appium automatically
pytest --app-reporting-enabled       # write artifacts/appium-pytest-kit/summary.json

See DOCUMENTATION.md § Configuration for the full settings table.


Expanded waits

# Element state
waiter.for_clickable(locator)                          # wait for element to be tappable
waiter.for_invisibility(locator)                       # wait for element to disappear

# Text matching
waiter.for_text_contains(locator, "partial text")      # wait for text substring
waiter.for_text_equals(locator, "exact text")          # wait for exact text match

# Collections
waiter.for_all_visible([loc1, loc2, loc3])             # wait for all elements to appear
waiter.for_all_gone([loc1, loc2])                      # wait for all elements to disappear
waiter.for_any_visible([loc1, loc2])                   # wait for first visible element

# Platform / context
waiter.for_context_contains("WEBVIEW")                 # wait for hybrid app webview context
waiter.for_android_activity("MainActivity")            # wait for Android activity

Expanded actions

# Tap variants
actions.tap_if_present(locator)                  # tap if visible — returns bool
actions.tap_if_present_first_available([l1, l2]) # tap first visible from list — returns bool
actions.tap_by_coordinates(x, y)                 # tap at screen pixel coordinates
actions.tap_center(locator)                      # tap the visual center of element
actions.double_tap(locator)                      # two quick taps
actions.long_press(locator, duration_seconds=2)  # hold press

# Text input
actions.type_if_present(locator, "text")                  # type if visible — returns bool
actions.type_if_present_first_available([l1, l2], "text") # type into first visible — returns bool
actions.type_first_available([l1, l2], "text")            # type into first visible (raises on fail)
actions.type_text_slowly(locator, "text", delay_per_char=0.1)  # char-by-char typing
actions.clear(locator)                                     # clear a text field

# Assertions
actions.is_displayed(locator)                                        # bool — element is visible on screen
actions.assert_displayed(locator)                                    # raises AssertionError if not visible
actions.is_displayed_first_available([l1, l2])                       # bool — any locator visible
actions.assert_displayed_first_available([l1, l2])                   # raises if none visible
actions.not_displayed_first_available([l1, l2])                      # bool — none of the locators visible
actions.assert_not_displayed_first_available([l1, l2])               # raises if any visible

# Read
actions.attribute(locator, "content-desc")               # read element attribute

# Scroll / swipe
actions.swipe(sx, sy, ex, ey)                            # raw W3C swipe gesture
actions.scroll_down()                                     # swipe up on screen center
actions.scroll_up()                                       # swipe down on screen center
actions.scroll_to_element(locator)                        # scroll until element visible

# Keyboard
actions.hide_keyboard()                                   # dismiss soft keyboard
actions.press_keycode(66)                                 # Android keycode (66=ENTER, 4=BACK)

# Hybrid / WebView
actions.is_webview_available()                            # bool — WEBVIEW context exists
actions.switch_to_webview()                               # switch to WEBVIEW context
actions.switch_to_native()                                # switch back to NATIVE_APP

Extension hooks

Implement these in your conftest.py to customise behaviour without touching the framework:

# conftest.py

def pytest_appium_pytest_kit_capabilities(capabilities, settings):
    """Add extra capabilities before each driver session."""
    return {"autoGrantPermissions": True, "language": "en"}


def pytest_appium_pytest_kit_configure_settings(settings):
    """Replace settings at session start."""
    return settings.model_copy(update={"implicit_wait": 2.0})


def pytest_appium_pytest_kit_driver_created(driver, settings):
    """Run setup immediately after each driver is created."""
    driver.orientation = "PORTRAIT"

Project scaffolding

Generate a full project structure in one command:

appium-pytest-kit-init --framework --root my-project

Creates:

my-project/
├── data/devices.yaml          # device profiles
├── pages/
│   ├── base_page.py           # BasePage composition class
│   └── example_page.py        # starter page object
├── flows/                     # reusable multi-step flows
├── tests/
│   ├── android/test_smoke.py
│   └── ios/test_smoke.py
├── conftest.py
├── pytest.ini
└── .env.example

Public API

Top-level imports (stable):

from appium_pytest_kit import (
    AppiumPytestKitSettings,
    AppiumPytestKitError,
    ConfigurationError,
    DeviceResolutionError,
    LaunchValidationError,
    WaitTimeoutError,
    ActionError,
    DriverCreationError,
    DeviceInfo,
    DriverConfig,
    MobileActions,
    Waiter,
    build_driver_config,
    create_driver,
    load_settings,
    apply_cli_overrides,
)

Stable public modules (direct import):

  • appium_pytest_kit.settings
  • appium_pytest_kit.driver
  • appium_pytest_kit.waits
  • appium_pytest_kit.actions
  • appium_pytest_kit.errors
  • appium_pytest_kit.interfacesCapabilitiesAdapter protocol for custom adapters

Private/internal modules (no compatibility guarantee):

  • appium_pytest_kit._internal.*

Fixture lifecycle

flowchart TD
    A["pytest start"] --> B["load defaults + .env + env vars"]
    B --> C["apply --app-* CLI overrides"]
    C --> D["settings fixture (session)"]
    D --> E{"APP_MANAGE_APPIUM_SERVER"}
    E -->|"true"| F["start local Appium server"]
    E -->|"false"| G["use APP_APPIUM_URL"]
    F --> H["appium_server fixture"]
    G --> H
    H --> I{"session_mode"}
    I -->|"clean-session / debug"| J["_driver_shared (session)"]
    I -->|"clean"| K["driver per test"]
    J --> K
    K --> L["waiter / actions fixtures"]
    K --> M["test executes"]
    M --> N{"failed?"}
    N -->|"yes"| O["capture screenshot + page source"]
    N --> P["video stop (per policy)"]
    O --> P
    P --> Q["driver.quit() if clean mode"]
    Q --> R["optional report summary flush"]
    R --> S["optional server stop"]

Local development

pip install -e ".[dev]"
ruff check .
pytest -q
pytest --collect-only examples/basic/tests -q

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

appium_pytest_kit-0.1.1.tar.gz (28.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

appium_pytest_kit-0.1.1-py3-none-any.whl (29.1 kB view details)

Uploaded Python 3

File details

Details for the file appium_pytest_kit-0.1.1.tar.gz.

File metadata

  • Download URL: appium_pytest_kit-0.1.1.tar.gz
  • Upload date:
  • Size: 28.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for appium_pytest_kit-0.1.1.tar.gz
Algorithm Hash digest
SHA256 ca029ae0656c122d84edbe4611c1546333d4e1e7bc8a0a9d553cb058232dfef9
MD5 a8e0d3231ebb1f9602d49450190d443e
BLAKE2b-256 ca799020bd107d79c25978676458e421217a4f3fc4ed7c432e7fba0a5b580e5e

See more details on using hashes here.

File details

Details for the file appium_pytest_kit-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for appium_pytest_kit-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8857adb8a95b6f05ef4f11f3dfa85bc98e95f24d609c24210657a2446dcfeb7a
MD5 da7b964718b6aba4c9ef6b885200d8c8
BLAKE2b-256 a6b8e1e42736ed9b45a1f414e981ab3e9880ab36797eaac69bbae1ea735aabdc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page