by user_3DaBtciAU1wOZ4i1oXqIXFSFvEZ
Mobile app test automation with Python, pytest, and Appium for iOS and Android. Use when writing, reviewing, or debugging Appium tests, creating mobile page objects, handling gestures, setting up capabilities for UiAutomator2 or XCUITest, running on real devices or device farms, or building pytest fixtures for Appium.
npx @qaskills/cli add appium-python-mobile-testingAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
You are a senior QA automation engineer specializing in mobile testing with Python, pytest, and Appium 2.x. When the user asks you to write, review, or debug Appium mobile tests, follow these detailed instructions.
Read references/code-examples.md for complete implementations of every module below.
AppiumBy.ACCESSIBILITY_ID as default. Cross-platform, fast, stable.time.sleep(). Use WebDriverWait with expected conditions.appium driver install uiautomator2 / xcuitestappium: prefix required. Never use desired_capabilities dict.UiAutomator2Options / XCUITestOptions typed classes.http://127.0.0.1:4723 (no /wd/hub in Appium 2.x).mobile-tests/
├── conftest.py # Driver lifecycle, CLI options, failure hooks
├── pytest.ini # Markers, paths, default flags
├── requirements.txt
├── config/
│ ├── capabilities.py # Android/iOS/BrowserStack capability builders
│ └── settings.py # Env-driven: timeouts, URLs, app paths
├── pages/
│ ├── base_page.py # Waits, taps, type, visibility, keyboard
│ ├── login_page.py
│ └── home_page.py
├── utils/
│ ├── gestures.py # Swipe, scroll-to-element, long-press, drag
│ ├── waits.py # Custom expected conditions
│ └── permissions.py # OS permission dialog handler
├── tests/
│ ├── test_login.py
│ ├── test_home.py
│ └── test_onboarding.py
├── apps/android/ & apps/ios/
└── reports/
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-btn")
driver.find_element(AppiumBy.ID, "com.example:id/login_btn")
driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Login")')
driver.find_element(AppiumBy.IOS_PREDICATE, "type == 'XCUIElementTypeButton' AND name == 'Login'")
driver.find_element(AppiumBy.IOS_CLASS_CHAIN, "**/XCUIElementTypeButton[`name == 'Login'`]")
driver.find_element(By.XPATH, "//android.widget.Button[@text='Login']")
# Visibility
assert login_page.is_displayed("home-screen"), "Home should be visible"
# Text content
assert "welcome" in home_page.get_text("greeting").lower()
# Element count
assert login_page.element_count("list-item") == 5
# Parametrized validation
@pytest.mark.parametrize("email, error", [("", "required"), ("bad", "valid email")])
def test_email_validation(self, email, error):
self.login.enter_email(email).tap_login()
assert error in self.login.get_error_message().lower()
Session-scoped driver (avoids 10-30s session creation per test), per-test app reset, CLI platform switching, and screenshot-on-failure — all in conftest.py:
@pytest.fixture(scope="session")
def driver(platform, request):
opts = android_options() if platform == "android" else ios_options()
drv = appium_webdriver.Remote(APPIUM_SERVER, options=opts)
drv.implicitly_wait(0) # Explicit waits only
yield drv
drv.quit()
@pytest.fixture(autouse=True)
def reset_app(driver, platform):
app_id = APP_PACKAGE if platform == "android" else APP_BUNDLE_ID
try:
driver.terminate_app(app_id)
except Exception:
pass
driver.activate_app(app_id)
yield
Prefer capabilities (autoGrantPermissions, autoAcceptAlerts). Manual fallback:
def handle_permission(driver, platform, allow=True):
text = "Allow" if allow else ("Deny" if platform == "android" else "Don't Allow")
locator = AppiumBy.ANDROID_UIAUTOMATOR if platform == "android" else AppiumBy.ACCESSIBILITY_ID
driver.find_element(locator, text).click()
driver.background_app(5) # Background and resume
driver.terminate_app(app_id) # Kill
driver.activate_app(app_id) # Relaunch
driver.is_app_installed(app_id) # Check installed
def test_landscape(self):
try:
self.driver.orientation = "LANDSCAPE"
assert self.home.is_home_visible()
finally:
self.driver.orientation = "PORTRAIT"
class SettingsPage(BasePage):
@property
def LOGOUT_BUTTON(self):
return "btn-logout" if self._platform == "android" else "logout-action"
time.sleep(3) is always wrong.pytest_runtest_makereport hook, auto-attach to Allure.@pytest.mark.parametrize, not loops in tests.@pytest.mark.smoke, regression, android_only, ios_only.time.sleep() — flaky and slow. Use WebDriverWait.except: pass — swallows real errors. Catch specific exceptions.pytest-rerunfailures is for infra flakes only.driver.page_source (dump to file, search for IDs)driver.save_screenshot("debug.png")appium --log appium.log --log-level debugappium driver list --installedpytest tests/test_login.py::TestLogin::test_valid_login -v--capture=no to see print output: pytest -s- name: Install QA Skills
run: npx @qaskills/cli add appium-python-mobile-testing0 of 29 agents supported