Reusable Appium 2.x + pytest mobile test framework
Project description
appium-pytest-kit
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-initto bootstrap configuration, or--frameworkto 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)
- Explicit —
APP_DEVICE_NAME/APP_UDIDset in.envor CLI - Profile —
APP_DEVICE_PROFILE=pixel7resolved fromdata/devices.yaml - Auto-detect —
adb devices(Android) orxcrun 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:
- Screenshot →
artifacts/screenshots/<test_id>.png - Page source →
artifacts/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.settingsappium_pytest_kit.driverappium_pytest_kit.waitsappium_pytest_kit.actionsappium_pytest_kit.errorsappium_pytest_kit.interfaces—CapabilitiesAdapterprotocol 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca029ae0656c122d84edbe4611c1546333d4e1e7bc8a0a9d553cb058232dfef9
|
|
| MD5 |
a8e0d3231ebb1f9602d49450190d443e
|
|
| BLAKE2b-256 |
ca799020bd107d79c25978676458e421217a4f3fc4ed7c432e7fba0a5b580e5e
|
File details
Details for the file appium_pytest_kit-0.1.1-py3-none-any.whl.
File metadata
- Download URL: appium_pytest_kit-0.1.1-py3-none-any.whl
- Upload date:
- Size: 29.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8857adb8a95b6f05ef4f11f3dfa85bc98e95f24d609c24210657a2446dcfeb7a
|
|
| MD5 |
da7b964718b6aba4c9ef6b885200d8c8
|
|
| BLAKE2b-256 |
a6b8e1e42736ed9b45a1f414e981ab3e9880ab36797eaac69bbae1ea735aabdc
|