Skip to main content

Appium integration for the tquality test automation framework, built on tquality-py-core.

Project description

tquality-py-appium

PyPI License GitHub

Appium integration for the tquality test automation framework, built on tquality-py-core.

Page-object style mobile UI testing for Android and iOS: typed elements with built-in smart waits, a dependency-injection composition root, cascading JSON5 config, native/webview switching, and Allure reporting with screencasts.

Русская версия

Installation

pip install tquality-py-appium
# or
uv add tquality-py-appium

Requires Python >= 3.12 and a running Appium server (local or remote grid / cloud such as BrowserStack, SauceLabs).

Quickstart

1. Generate config files

# config.json5 - framework behavior defaults (timeouts, context, screencast)
tquality-appium-config init

# capabilities.json5 - devices + applications
tquality-appium-config caps-init

2. Describe your devices and apps

capabilities.json5 (json5: comments and trailing commas allowed):

{
    "$schema": "https://cdn.jsdelivr.net/gh/Tquality-ru/tquality-py-appium@master/schema/capabilities.schema.json",
    "selectedDevice": "local_android",
    "selectedApplication": "demo_app",
    "devices": {
        "local_android": {
            "location": "http://127.0.0.1:4723",
            "capabilities": {
                "appium:platformName": "Android",
                "appium:automationName": "UiAutomator2",
                "appium:deviceName": "emulator",
                "appium:autoGrantPermissions": true,
            },
        },
    },
    "applications": {
        "demo_app": {
            "appium:appPackage": "com.example.shop",
            "appium:appActivity": "com.example.shop.MainActivity",
        },
    },
}

The active device/app pair is picked via selectedDevice / selectedApplication, or overridden at runtime — handy for a CI device matrix:

TEST_SELECTED_DEVICE=pixel_8 TEST_SELECTED_APPLICATION=demo_app_prod pytest

3. Wire up the composition root

conftest.py at your project root registers the DI container once:

from pathlib import Path

from tquality_appium import AppiumServices

AppiumServices.setup(config_dir=Path(__file__).resolve().parent)

A per-test fixture starts and tears down the Appium session:

# tests/conftest.py
from collections.abc import Iterator

import pytest

from tquality_appium import AppiumDriverService, AppiumServices


@pytest.fixture(autouse=True)
def appium_session() -> Iterator[AppiumDriverService]:
    svc = AppiumServices.driver()
    try:
        yield svc
    finally:
        svc.quit()
        AppiumServices.driver.reset()
        AppiumServices.logger.reset()
        AppiumServices.waiter.reset()

Key features

Page objects with BaseForm

A screen is a class; its unique_element defines "this screen is shown". Tests call business methods, never raw elements:

from tquality_appium import BaseForm, By


class LoginForm(BaseForm):
    def __init__(self) -> None:
        super().__init__(
            unique_element=self.element_factory.label(
                By.xpath('//*[@text="Sign in"]'), "Login form",
            ),
            name="Login form",
        )
        self._phone = self.element_factory.input(
            By.accessibility_id("phone_input"), "Phone number",
        )
        self._submit = self.element_factory.button(
            By.accessibility_id("login_button"), "Login",
        )

    def login(self, phone: str) -> None:
        self._phone.type_text(phone)
        self._submit.click()

Typed elements and locator strategies

element_factory builds Button / Input / Label / CheckBox. The By factory covers cross-platform and native strategies:

from tquality_appium import By, UiScrollable, UiSelector

By.accessibility_id("login_button")
By.xpath('//XCUIElementTypeButton[@name="Continue"]')
By.ios_predicate("name == 'Login'")

# Android UiAutomator via a fluent builder
By.android_uiautomator(UiSelector().resource_id("com.example.shop:id/ok"))

# Scroll until the element comes into view
By.android_uiautomator(
    UiScrollable().scroll_into_view(UiSelector().text_contains("Checkout"))
)

Elements expose intent-level actions — click(), type_text(), check() / toggle(), .text, .is_displayed — each with built-in waits.

Smart waits and element state

Every interaction waits for the right precondition first. Each element carries an ElementState (e.g. buttons default to CLICKABLE, labels to DISPLAYED); override per element when the default gets in the way:

from tquality_appium import By, ElementState

# A tab whose unique element reports displayed == false — only require it to exist
self._tab = self.element_factory.button(
    By.xpath('//*[contains(@resource-id, "tab_profile")]'),
    "Profile tab",
    state=ElementState.EXISTS_IN_ANY_STATE,
)

# Explicit waits are available when you need them
self._submit.wait.until_visible(timeout=10)
assert LoginForm().wait_for_displayed(raise_on_timeout=True)

Allure steps with screencast

@step records an Allure step. At LogLevel.WITH_SCREENCAST the framework captures video for that step (native start_recording_screen, with an automatic webm fallback when on-device recording is blocked). Page source is attached to the report on failure.

import allure

from tquality_appium import LogLevel, step

from myapp.screens.home_page import HomePage
from myapp.screens.login_form import LoginForm


class TestNavigation:
    @allure.title("Profile tab opens the login form for a guest")
    @step("Open the profile tab", LogLevel.WITH_SCREENCAST)
    def test_profile_requires_login(self) -> None:
        home = HomePage()
        assert home.wait_for_displayed(), "Home not displayed on launch"
        home.navigation_menu.open_profile()
        assert LoginForm().wait_for_displayed(), "Login form not displayed"

Native ↔ webview, alerts and windows

ContextManager handles native/webview switching; context.wait.for_alert waits on system dialogs (permission prompts, iOS ATT, etc.):

from tquality_appium import AppiumServices, ContextManager

ctx = AppiumServices.get_service(ContextManager)
with ctx.context("WEBVIEW_com.example.shop"):
    ...  # interact inside the embedded webview

# Wait for a system permission dialog and accept it
alert = AppiumServices.driver().context.wait.for_alert(
    lambda a: "Allow" in a.text,
    message="permission prompt",
)
if alert:
    alert.accept()

Collections → Pydantic models

Pull a whole UI list into typed models in a single round-trip (via execute_driver_script, with an automatic per-field fallback if the server disables it):

from pydantic import BaseModel

from tquality_appium import AppiumServices, By, CollectionFactory, DomField


class Product(BaseModel):
    title: str = DomField.id("com.example.shop:id/title")
    price: str = DomField.id("com.example.shop:id/price")


factory = AppiumServices.get_service(CollectionFactory)
products = factory.from_container(Product, By.class_name("android.widget.ListView"))

Dependency-injection composition root

AppiumServices is the single composition root. Resolve services by type (rename-safe), or subclass to add/replace services:

from dependency_injector import providers

from tquality_appium import AppiumServices, ContextManager


class ProjectServices(AppiumServices):
    my_service = providers.Singleton(MyService)


# Resolve by type, not by provider name
ctx = AppiumServices.get_service(ContextManager)

Cascading JSON5 config and per-test devices

config.json5 (framework behavior) and capabilities.json5 (infrastructure) are resolved upward from the test's directory to the workspace root. This lets tests/ios/ and tests/android/ each ship their own capabilities.json5 (iOS simulator vs Android emulator) while common settings live at the root — configs are rebuilt per test automatically.

Documentation

See tquality-py-core for the driver-agnostic concepts (BaseConfig, Logger, BaseForm, BaseElement, JSON-schema cascading config) — everything from core is re-exported here.

Appium-specific:

  • AppiumConfig — framework behavior (timeouts, default context, screencast)
  • CapabilitiesConfig — devices + applications, loaded from capabilities.json5
  • AppiumDriverService — manages the appium.webdriver.Remote session
  • ContextManager / ContextWaiter — native/webview switching, alerts, windows
  • ElementFactory — typed element creation (Button / Input / Label / CheckBox)
  • CollectionFactory + DomField — extract list[PydanticModel] from UI lists
  • ExecuteDriver — batched commands via execute_driver_script
  • AppiumServices — DI composition root

License

Apache-2.0. See LICENSE and NOTICE.

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

tquality_py_appium-0.1.9.tar.gz (103.9 kB view details)

Uploaded Source

Built Distribution

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

tquality_py_appium-0.1.9-py3-none-any.whl (92.6 kB view details)

Uploaded Python 3

File details

Details for the file tquality_py_appium-0.1.9.tar.gz.

File metadata

  • Download URL: tquality_py_appium-0.1.9.tar.gz
  • Upload date:
  • Size: 103.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tquality_py_appium-0.1.9.tar.gz
Algorithm Hash digest
SHA256 cea237541aadabd496d6193f6a4f895f6132a022dd88eaea470737f72190d88f
MD5 c0c057cc2621ea7bf48ea4b28aa85f84
BLAKE2b-256 9b718b64dd0d26133fe79e3844add2becf9d160b2f518521e0178e5809442942

See more details on using hashes here.

File details

Details for the file tquality_py_appium-0.1.9-py3-none-any.whl.

File metadata

  • Download URL: tquality_py_appium-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 92.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tquality_py_appium-0.1.9-py3-none-any.whl
Algorithm Hash digest
SHA256 0ecd647d8d42429e3e1225ed39bb75c030decee504442606e8bbecb27714feb5
MD5 a3da196aa2eee983c0f34ba526e8bf7d
BLAKE2b-256 6fd0c5a55f30cc7e14ecd700482d5530dfad9afcd8ec3e333ad9e4219403a83a

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