コンテンツにスキップ

English · 日本語

セレクタと決定的解決(決定性の核)

「どの要素を操作または検証するか」をどう指定し、どう一意に確定するかを説明します。Bajutsu の決定性はこのモジュールに集約されています。すべての実行系(orchestrator / drivers / assertions)がここに依存します。

実装: bajutsu/drivers/base.py

関連: concepts の決定性原則 · scenarios の DSL · drivers


正規化された要素(Element

ドライバはバックエンドの出力を共通の Element(TypedDict)へ正規化します。解決とアサーションはこの正規化形だけを参照します(バックエンド差はドライバ側で吸収済みです)。

class Element(TypedDict):
    identifier: str | None        # 安定 id(iOS は accessibilityIdentifier・web は data-testid)
    label: str | None             # accessibilityLabel
    traits: list[str]             # 正規化トレイト(下記)
    value: str | None             # accessibility value
    frame: tuple[float, float, float, float]  # x, y, w, h(points)

正規化トレイト(Trait

状態アサーションが参照する共通トークンです。ドライバは少なくとも次を正規化します:

トークン 意味 使うアサーション
button / link 種別 traits セレクタ、doctor の actionable 判定
notEnabled 無効状態 enabled / disabled
selected 選択 / トグル ON selected

(idb は enabled: falsenotEnabledselected: trueselected に正規化します。種別文字列は AX 接頭辞を外して先頭を小文字化します: AXButtonbutton。詳細は drivers/idb.py を参照してください)

セレクタ(Selector

要素のアドレス指定に使います。指定したフィールドはすべて AND で適用されます。

フィールド 意味 安定性
id accessibilityIdentifier の完全一致。リストは候補の OR(いずれかに一致) ★ 第一候補
idMatches id の glob パターン(複数マッチ前提。例 "list.row.*")。リストはいずれかの glob に一致すればよい 集合操作用
label accessibilityLabel の完全一致 補助 / 曖昧解消のみ
labelMatches label の部分一致 / 正規表現(re.search 補助
traits トレイトで絞る(部分集合判定。例 ["button"] 補助
value accessibility value の完全一致 補助
within コンテナでスコープ限定(幾何: 候補の frame が within の解決先の内側にあること。ネスト可) 一意化
index 複数マッチ時の n 番目(負数可) 最終手段、フレーキー

id / idMatches のマッチは fnmatch.fnmatchcase(大小区別あり glob)、labelMatchesre.search(正規表現 / 部分一致)、traits は「指定集合 ⊆ 要素のトレイト集合」です。

id / idMatches候補のリストも受け付けます。OR として、要素の id がいずれかの候補に一致(または glob 一致)すればマッチします(BE-0221)。これにより 1 つの共有シナリオがプラットフォームごとに異なる id 表記を持てます(例: Android Views の android:id./- を許さないので id: [stable.refresh, stable_refresh])。あるアプリの画面に現れる形は常に一方だけなので決定的なままで、2 件以上一致すれば従来どおり即失敗します。scenarios を参照してください。

オーサリング表現と実行時表現

  • シナリオ YAML 側のセレクタscenario/models/selector.pySelector(pydantic、idMatches 等の alias を持つ)です。
  • 解決に渡るのは drivers/base.pySelector(TypedDict)です。
  • 変換は Selector.as_selector() で行います(None を除いて TypedDict 化)。

解決セマンティクス

query() で得た要素リストにセレクタを適用して候補を絞ります。3 つの公開関数があります。

matches(el, sel) -> bool

1 要素が要素単位の条件を満たすかを返します(AND)。within は要素横断(空間)の制約で、find_all 側で解決します。

find_all(elements, sel) -> list[Element]

一致する すべて の要素を返します。idMatches トリガーや count アサーション、exists 判定に使います(複数マッチを許容します)。

resolve_unique(elements, sel) -> Element

単一アクション用に、ちょうど 1 件へ確定します。 曖昧一致による非決定性をここで断つ、決定性の核となる関数です。

候補数 挙動
0 件 ElementNotFound(即時アクションは失敗、待機(wait_until)経由はタイムアウト)
1 件 解決成功
2 件以上 AmbiguousSelector を送出。「たまたま最初の一致を叩く」非決定性を構造的に排除する

例外として index が指定されたときだけ、複数候補から n 番目を選びます(範囲外は ElementNotFound)。index は順序変化で壊れるため最終手段です。集合を扱う場合は idMatches + count を使ってください(scenarios)。

# drivers/base.py(抜粋)
def resolve_unique(elements, sel):
    candidates = find_all(elements, sel)
    if "index" in sel:
        ...                         # n 番目(範囲外は ElementNotFound)
    if not candidates:
        raise ElementNotFound(...)
    if len(candidates) > 1:
        raise AmbiguousSelector(...)  # within か index で一意化が必要
    return candidates[0]

例外階層: SelectorError(基底) ← ElementNotFound / AmbiguousSelector。orchestrator と assertions はこれを捕捉して「ステップ失敗」「アサーション失敗」に変換します(例外を上に投げません)。

バックエンドに依らず一元化される

idb は使える semantic tap を持たないため、抽象側は 常に query() で候補数を検証してから操作し、確定した要素の frame 中心をタップします。これにより「曖昧なら失敗」の挙動が idb / playwright / fake で同一になります(各ドライバの tap 実装は drivers を参照してください)。

id は idb の要素ツリー(AXUniqueId)から直接得られ、Element.identifier に正規化されます。そのため id セレクタは正規化形に対して直接解決できます。

アサーション評価

実装: bajutsu/assertions/evaluate.py。BE-0250 で単一モジュールから分割)。evaluate(elements, assertions) -> list[AssertionResult] が各アサーションを評価し、passed(results) が AND を取ります。評価は総関数で、解決失敗(not-found / ambiguous)も例外でなく「失敗した AssertionResult」として返します(そのままレポートに載ります)。

@dataclass(frozen=True)
class AssertionResult:
    ok: bool
    kind: str        # "exists" / "value" / ...
    detail: str      # 何を検査したか(レポート用)
    reason: str      # 失敗理由(ok のとき空)

種別ごとの仕組み(全 8 種別):

種別 解決 判定
exists find_all で 1 件以上か found != negate(負論理で不在検証)
value resolve_unique(曖昧 / 不在は失敗) valueequals/contains/matches で比較
label 同上 label を同様に比較
count find_all の件数 equals/atLeast/atMost
enabled resolve_unique notEnabled トレイトが 無い
disabled resolve_unique notEnabled トレイトが 有る
selected resolve_unique selected トレイトが有る
request 観測した通信を照合(要素ツリーではない) count 指定時は equals/atLeast/…、無指定なら 1 件以上(network

exists だけ find_all(複数許容)を使い、他の単一要素アサーションは resolve_unique(曖昧は失敗)を使います。「2 件あるのに値を検証しようとした」場合も決定的に失敗します。request だけが非 UI のアサーションで、要素ではなくキャプチャした HTTP(S) 通信を検査します。