本文へ移動
cccskills
無料GitHub で公開

appium-test-authoring

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.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md9.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Appium Test Authoring

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.kt
  • docs/automation/selector-contract.md

Directory Layout

appium/
  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

Creating a Page Object

File: appium/pages/{screen}_page.py

Checklist:

  1. Module docstring: """{Screen name} screen page object."""
  2. from __future__ import annotations + from .base_page import BasePage
  3. Class inherits BasePage
  4. SCREEN = "{screen}-screen" class constant
  5. All locators as SCREAMING_SNAKE class constants
  6. is_loaded() method: return self.is_visible(self.SCREEN)
  7. Semantic action methods (no raw driver calls)
  8. No assertions -- expose state via is_*_visible() methods

Tag Naming Pattern

ComponentPatternExample
Screen root{screen}-screenhome-screen
Button{screen}-{action}[-button]home-connection-button
Input field{screen}-{field}dns-plain-address
Toggle{screen}-{name}-togglesettings-webrtc-toggle
Card{screen}-{name}-cardhome-approach-card
Dialog{component}-dialogvpn-permission-dialog
Dialog action{component}-dialog-{action}vpn-permission-dialog-continue
Dynamic{screen}-{type}-{identifier}dns-resolver-cloudflare

Template

"""{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)

Method Naming Conventions

ActionPatternReturn
Tap/clicktap_{component}()None
Type textset_{field}(value)None
Clear + typefill_{field}(value) or set_{field}(value)None
Check visibilityis_{component}_visible()bool
Select optionselect_{option}(value)None
Toggle switchtoggle_{setting}()None
Navigate awaytap_{destination}()destination page object (optional)
Scroll to elementuse self.scroll_to(TAG) internallyNone or element

BasePage API

Source: appium/pages/base_page.py

MethodSignatureDefaultNotes
_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) -> WebElement10sExplicit wait via EC.presence_of_element_located
is_visible(tag: str, timeout: int = 3) -> bool3sReturns False on timeout, never raises
tap(tag: str, timeout: int = 10) -> None10swait_for then .click()
clear_and_type(tag: str, text: str, timeout: int = 10) -> None10s.clear() then .send_keys(text)
swipe_horizontal(direction: str = "left") -> NoneleftSwipes across screen center, 300ms duration
scroll_to(tag: str, max_swipes: int = 5) -> WebElement5 swipesSwipes down until visible, returns element

Locator Conventions

  • Only use AppiumBy.ID with resource-id tags. Never XPath, class name, or accessibility-id.
  • Tags without : are auto-prefixed by _resource_id() to com.poyka.ripdpi:id/{tag}.
  • Tags map to Modifier.testTag("{tag}") in Jetpack Compose source.
  • Keep tags short and descriptive. Use hyphens, not underscores (except for screen names that mirror route names like dns_settings).
  • For diagnostics strategy reports, prefer the dedicated stable tags rather than visible text, for example diagnostics-strategy-winning-path, diagnostics-strategy-full-matrix-toggle, and diagnostics-workflow-restriction-action.

Creating a Test File

File: appium/tests/test_{NN}_{description}.py (use next sequential number)

Checklist:

  1. Module docstring: """Test {NN}: {description}."""
  2. import pytest + import page object(s)
  3. @pytest.mark.automation(...) with keyword params
  4. Function: def test_{descriptive_name}(driver):
  5. Instantiate page object: page = PageClass(driver)
  6. Assert screen loaded: assert page.is_loaded(), "Screen should be visible"
  7. Interact via page object methods
  8. Assert outcomes with descriptive failure messages
  9. For multi-page flows: instantiate new page object after navigation

Template

"""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"

Multiple Tests per File

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"

Assertion Patterns

# 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:

  • Always assert (not unittest assertions)
  • Always include a failure message string
  • Assertions only in test functions, never in page objects

Wait Strategy

TierTimeoutUse For
Short3sis_visible() probes, elements expected on current screen
Medium10swait_for(), tap(), normal element appearance
Long15sScreen launch in conftest.py only
  • Never use time.sleep() in test files
  • Never use implicit waits
  • Override timeout per-call when needed: page.wait_for("slow-element", timeout=15)

Updating Existing Tests

ChangeAction
Locator tag renamedUpdate the class constant in the page object only
New element on existing screenAdd constant + semantic method to page object, add test
Screen addedCreate new page object + new test file
Screen removedDelete page object + test file
Navigation path changedUpdate test flow, not page object API
Preset values changedUpdate marker params in affected tests

Common Mistakes

MistakeFix
Using XPath or class name locatorsUse AppiumBy.ID with resource-id tags only
Assertions in page objectsMove to test functions; page objects expose state
time.sleep() in testsUse wait_for() or is_visible() with timeout
Missing failure message on assertAlways provide descriptive string
Hardcoding com.poyka.ripdpi:id/ prefixUse tag shorthand; _resource_id() adds prefix
Skipping is_loaded() checkAlways verify screen loaded as first assertion
Monolithic tests testing many featuresOne behavior per test function
Missing @pytest.mark.automationEvery test needs the marker for the launch contract

See Also

  • .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).

日本語の概要は準備中です。原文の説明を表示しています。

po4yka/RIPDPI792026年10月13日 更新

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.

日本語の概要は準備中です。原文の説明を表示しています。

po4yka/RIPDPI792026年10月13日 更新

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.

日本語の概要は準備中です。原文の説明を表示しています。

po4yka/RIPDPI792026年10月13日 更新

RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.

日本語の概要は準備中です。原文の説明を表示しています。

po4yka/RIPDPI792026年10月13日 更新

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).

日本語の概要は準備中です。原文の説明を表示しています。

po4yka/RIPDPI792026年10月13日 更新

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.

日本語の概要は準備中です。原文の説明を表示しています。

po4yka/RIPDPI792026年10月13日 更新

po4yka のスキルをすべて見る

このスキルの問題を報告する