RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
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 |
.agents/skills/appium-automation-contract/SKILL.md -- Preset values and marker parameters.agents/skills/appium-test-debug/SKILL.md -- Troubleshooting failures and flakiness아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Conservative review of end-user legal exposure from diagnostics targets and probe hosts, by jurisdiction. Use when shipping or editing diagnostics target lists, probe hosts, or bootstrap endpoints.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.