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: false → notEnabled、selected: true → selected に正規化します。種別文字列は AX 接頭辞を外して先頭を小文字化します: AXButton → button。詳細は 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)、labelMatchesはre.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.pyのSelector(pydantic、idMatches等の alias を持つ)です。 - 解決に渡るのは
drivers/base.pyのSelector(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(曖昧 / 不在は失敗) |
value を equals/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) 通信を検査します。