بنقرة واحدة
appium-test-authoring
Appium test authoring, page objects, locators, assertions, and new-screen coverage.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Appium test authoring, page objects, locators, assertions, and new-screen coverage.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Use this skill to generate well-branded interfaces and assets for RIPDPI (an Android-native, Compose-first VPN and DPI-bypass app), either for production or throwaway prototypes/mocks/etc. Contains essential design guidelines, colors, type, fonts, brand-ship icons, and an interactive UI kit of every public composable from the live ui/components tree.
Use when managing the Rust workspace, adding/removing crates, editing workspace dependencies, running cargo nextest/audit/deny, configuring Cargo profiles for Android cross-compilation, debugging Cargo.lock churn, migrating crate edition, or wiring Gradle to cargo via the ripdpi.android.rust-native plugin.
Use when modifying the diagnostics scan pipeline, ScanRequest/ScanReport types, ProbeTask families, ripdpi-monitor-engine / ripdpi-diagnostics-* crates, strategy-probe candidates, the diagnostics catalog (packs/profiles), wire-schema contracts between Rust and Kotlin, DIAGNOSTICS_ENGINE_SCHEMA_VERSION, golden contract tests, or adding a new probe type / profile. Triggers on diagnostics scans, strategy probes, automatic audit, dpi-detector profiles, or anything in core/diagnostics or native/rust/crates/ripdpi-monitor-*.
Android-specific Rust build, verification, and packaging — per-target 16 KiB page alignment, size-optimized release profile, ELF symbol allowlist, .so size budgets, NDK 29 specifics. Use when modifying .cargo/config.toml for Android targets, the workspace [profile.release] / [profile.android-jni] block, or when verifying a built .so before release.
Telemetry and observability discipline for the RIPDPI Android Rust stack — control-plane vs data-plane logging, bounded flume event queues, atomic counters, snapshot polling, readiness callbacks, and deterministic JSON contracts. Use when authoring or modifying telemetry emission code, the bounded event ring, the Kotlin-side telemetry consumer, or any per-packet logging.
Custom detekt rules, DI/privacy/suppression guardrails, detekt.yml configuration, and false-positive triage.
| name | appium-test-authoring |
| description | Appium test authoring, page objects, locators, assertions, and new-screen coverage. |
Python pytest suite under appium/. Page Object Model with resource-id locators via AppiumBy.ID. Automation contract drives app state (see appium-automation-contract skill).
Tag source of truth:
app/src/main/kotlin/com/poyka/ripdpi/ui/testing/RipDpiTestTags.ktdocs/automation/selector-contract.mdappium/
conftest.py # Session driver + per-test launch fixture
pytest.ini # Pytest config and custom markers
requirements.txt # Pinned deps (Appium-Python-Client, pytest, selenium)
lib/
capabilities.py # UiAutomator2Options builder
launch_contract.py # Automation contract constants + arg builder
driver_helpers.py # Shared wait/find helpers
pages/
base_page.py # Base class -- all page objects inherit this
{screen}_page.py # One page object per screen
tests/
test_{NN}_{description}.py # Numbered test files
File: appium/pages/{screen}_page.py
Checklist:
"""{Screen name} screen page object."""from __future__ import annotations + from .base_page import BasePageBasePageSCREEN = "{screen}-screen" class constantSCREAMING_SNAKE class constantsis_loaded() method: return self.is_visible(self.SCREEN)is_*_visible() methods| Component | Pattern | Example |
|---|---|---|
| Screen root | {screen}-screen | home-screen |
| Button | {screen}-{action}[-button] | home-connection-button |
| Input field | {screen}-{field} | dns-plain-address |
| Toggle | {screen}-{name}-toggle | settings-webrtc-toggle |
| Card | {screen}-{name}-card | home-approach-card |
| Dialog | {component}-dialog | vpn-permission-dialog |
| Dialog action | {component}-dialog-{action} | vpn-permission-dialog-continue |
| Dynamic | {screen}-{type}-{identifier} | dns-resolver-cloudflare |
"""{Screen name} screen page object."""
from __future__ import annotations
from .base_page import BasePage
class ExamplePage(BasePage):
SCREEN = "example-screen"
TITLE = "example-title"
PRIMARY_BUTTON = "example-primary-button"
INPUT_FIELD = "example-input-field"
def is_loaded(self) -> bool:
return self.is_visible(self.SCREEN)
def tap_primary(self) -> None:
self.tap(self.PRIMARY_BUTTON)
def set_input(self, text: str) -> None:
self.clear_and_type(self.INPUT_FIELD, text)
def is_title_visible(self) -> bool:
return self.is_visible(self.TITLE)
| Action | Pattern | Return |
|---|---|---|
| Tap/click | tap_{component}() | None |
| Type text | set_{field}(value) | None |
| Clear + type | fill_{field}(value) or set_{field}(value) | None |
| Check visibility | is_{component}_visible() | bool |
| Select option | select_{option}(value) | None |
| Toggle switch | toggle_{setting}() | None |
| Navigate away | tap_{destination}() | destination page object (optional) |
| Scroll to element | use self.scroll_to(TAG) internally | None or element |
Source: appium/pages/base_page.py
| Method | Signature | Default | Notes |
|---|---|---|---|
_resource_id | (tag: str) -> str | -- | Prepends com.poyka.ripdpi:id/ if tag has no : |
find | (tag: str) -> WebElement | -- | Immediate lookup, no wait. Raises if not found. |
wait_for | (tag: str, timeout: int = 10) -> WebElement | 10s | Explicit wait via EC.presence_of_element_located |
is_visible | (tag: str, timeout: int = 3) -> bool | 3s | Returns False on timeout, never raises |
tap | (tag: str, timeout: int = 10) -> None | 10s | wait_for then .click() |
clear_and_type | (tag: str, text: str, timeout: int = 10) -> None | 10s | .clear() then .send_keys(text) |
swipe_horizontal | (direction: str = "left") -> None | left | Swipes across screen center, 300ms duration |
scroll_to | (tag: str, max_swipes: int = 5) -> WebElement | 5 swipes | Swipes down until visible, returns element |
AppiumBy.ID with resource-id tags. Never XPath, class name, or accessibility-id.: are auto-prefixed by _resource_id() to com.poyka.ripdpi:id/{tag}.Modifier.testTag("{tag}") in Jetpack Compose source.dns_settings).diagnostics-strategy-winning-path, diagnostics-strategy-full-matrix-toggle, and diagnostics-workflow-restriction-action.File: appium/tests/test_{NN}_{description}.py (use next sequential number)
Checklist:
"""Test {NN}: {description}."""import pytest + import page object(s)@pytest.mark.automation(...) with keyword paramsdef test_{descriptive_name}(driver):page = PageClass(driver)assert page.is_loaded(), "Screen should be visible""""Test NN: Description of what this test covers."""
import pytest
from pages.example_page import ExamplePage
@pytest.mark.automation(
start_route="example",
data_preset="settings_ready",
)
def test_example_feature(driver):
page = ExamplePage(driver)
assert page.is_loaded(), "Example screen should be visible"
page.set_input("test value")
page.tap_primary()
assert page.is_title_visible(), "Title should remain visible after action"
When testing different flows on the same screen, use separate functions with their own markers:
@pytest.mark.automation(start_route="dns_settings", data_preset="settings_ready")
def test_doh_resolver_selection(driver):
dns = DnsSettingsPage(driver)
assert dns.is_loaded(), "DNS settings should be visible"
dns.select_doh_resolver("cloudflare")
@pytest.mark.automation(start_route="dns_settings", data_preset="settings_ready")
def test_custom_doh_url(driver):
dns = DnsSettingsPage(driver)
assert dns.is_loaded(), "DNS settings should be visible"
dns.set_custom_doh_url("https://example.com/dns-query")
assert dns.is_custom_save_visible(), "Save button should appear"
# Visibility check -- always include failure message
assert page.is_visible(TAG), "Description of what should be true"
# Negative check
assert not page.is_visible(TAG), "Element should not be visible after action"
# Screen transition -- assert new page loaded after navigation
page.tap_settings()
settings = SettingsPage(driver)
assert settings.is_loaded(), "Settings screen should appear"
Rules:
assert (not unittest assertions)| Tier | Timeout | Use For |
|---|---|---|
| Short | 3s | is_visible() probes, elements expected on current screen |
| Medium | 10s | wait_for(), tap(), normal element appearance |
| Long | 15s | Screen launch in conftest.py only |
time.sleep() in test filespage.wait_for("slow-element", timeout=15)| Change | Action |
|---|---|
| Locator tag renamed | Update the class constant in the page object only |
| New element on existing screen | Add constant + semantic method to page object, add test |
| Screen added | Create new page object + new test file |
| Screen removed | Delete page object + test file |
| Navigation path changed | Update test flow, not page object API |
| Preset values changed | Update marker params in affected tests |
| Mistake | Fix |
|---|---|
| Using XPath or class name locators | Use AppiumBy.ID with resource-id tags only |
| Assertions in page objects | Move to test functions; page objects expose state |
time.sleep() in tests | Use wait_for() or is_visible() with timeout |
| Missing failure message on assert | Always provide descriptive string |
Hardcoding com.poyka.ripdpi:id/ prefix | Use tag shorthand; _resource_id() adds prefix |
Skipping is_loaded() check | Always verify screen loaded as first assertion |
| Monolithic tests testing many features | One behavior per test function |
Missing @pytest.mark.automation | Every test needs the marker for the launch contract |
.github/skills/appium-automation-contract/SKILL.md -- Preset values and marker parameters.github/skills/appium-test-debug/SKILL.md -- Troubleshooting failures and flakiness