MagicPodの要素指定（ロケータ）の入門（Xpath編）

この記事はスタンバイ [Advent Calendar 2024](https://qiita.com/advent-calendar/2024/stanby)の8日目の記事です。
昨日は、@weno さんの記事でした。

## MagicPodのメンテナンス時間の改善
より良いMagicPod生活を送りたくないですか。
スタンバイでは、QAチーム主導のE2Eテストの自動化で[MagicPod](https://magicpod.com/)を活用しています。

MagicPod導入によるメリットは言わずもがなで他記事に譲りますが、本記事ではスタンバイQAにおいて、テスト自動化を取り組む前に、知識として勉強しておけば良かった箇所を取り上げます。

## UI指定？要素指定？ロケータ？xpath？
MagicPod上、自動で動くブラウザの操作は、
　対象の画面を「①キャプチャ」し、
　操作したい「②UIを指定」して、
　どのような「③操作を行う」かコマンドで指定する、
のが基本です。

今回は、②に特化した記事となります。
「②UIを指定」するのがロケータであり、そのロケータ指定の方法に「xpath」や「cssセレクター」などがあります。
[MagicPodヘルプセンター> 新しいロケータを追加・編集する](https://support.magic-pod.com/hc/ja/articles/4408903612825-%E6%96%B0%E3%81%97%E3%81%84%E3%83%AD%E3%82%B1%E3%83%BC%E3%82%BF%E3%82%92%E8%BF%BD%E5%8A%A0-%E7%B7%A8%E9%9B%86%E3%81%99%E3%82%8B)

## なぜ先に知っておいた方が良かったか
・テストシナリオのステップの短縮ができた
・UIの改変に強いロケータ指定ができた
・メンテナンスコストを削減できた
・自プロダクトのHTMLの構造に対する知見が増えた

メリットを挙げるともっとありますが、端的に言えば、
「**過去の自分作のテストシナリオがイケてなくて、うんざりした**」からです。
そして、勉強コストもそれほど高くなかったが故の後悔に苛まれただけです。

ポイントを押さえられれば、記憶するボリュームは案外少ないもので、クリップボード管理ツールでチートすれば、算数や数学の公式みたいに記憶することすら不要で、使い方を覚えればいいだけです。

MagicPodを問わず、自前のツールを作成したり、他の自動化ツールを使用するに当たって、活用できる知識となりますので覚えておいて損はないです。

## htmlの構造を、概念で掴む

```html
<html>
  <head></head>
  <body>
    <div>
      <div>
        <div></div>
        <p></p>
        <span></span>
      </div>
    </div>
    <main>
      <div class="list">
        <div class="item">
          <a href="https://aaa.com/bbb">
            <h2 class="title card-title">タイトル</h2>
            <p class="info">会社名</p>
          </a>
        </div>
      </div>
    </main>
  </body>
</html>
```
htmlを壮大な家系図に例えると、タグと呼ばれる div や a は、いわば「名前」です。
先頭にある htmlタグ が始祖となります。
名前（タグ）は重複するので個人を識別するためには、順番だったり、他の個人情報を付け加えます。
個人（タグ）に個人情報（classやid）を紐づかせるとして、class は「職業」であり、id は「マイナンバー」として捉えましょう。

| タグ内 | 例え | html内 |
| --- | --- | --- |
| div | 名前 | 重複する |
| class | 職業 | 重複する |
| id | マイナンバー | 一意 |

個人の特定方法は、単純なものだと、例えば以下のような問いになります。

1. 始祖htmlさんの子供のbodyさん
1. bodyさんを親に持つmainさん
1. mainを祖先に持つ職業がitemなdivさん

実際のhtmlは、もっと複雑ですし、個人を特定するには多様に言い換え可能です。
マイナンバー（id）を全て振れば解決しそうですが、職業（class）指定だけでも充足します。そして、作成する楽しみすらも、やんわりと滲み出てきます。

## なぜ、必要か
例えば、頭から順に辿っていけば、間違うことはありません。しかし、重ねて言いますが、短く端的に言い換えることが可能です。
ロケータ決めは、そのUIをどう呼ぶかの表現を決めることです。

以下は同一人物です。
- 始祖 の 二人目の子供body の 子供main の 最初の子供div さん
- 職業 list な div さん

ただし、「divさん」だけだと短か過ぎて複数ヒットし、完全に個人を特定できなくなる可能性が出てきます。
また、家系図は絶対的に不変ではなく、「実は子ではなく、孫でした！」という形で書き換えられます。
listという職業がよく目にする職業なら、ふたり目の「職業が list な div さん」が、あとからひょこっと出てきて、我が権利を主張する可能性だってあるのです。

成功していたテストが、なぜ失敗するようになったのか。
近い将来、また失敗するように、やみくもにメンテすることは得策ではありません。

ここで、本記事の目標地点を再度定めるとしたのなら、
「家系図の改変に強い個人の特定方法を、スタイリッシュに決めたい！」
と見据えましょう

## 覚えればいいやつ
まずは、これだけ覚えれば、世界が変わります。

| 使い方 | 書き方 |
| --- | --- |
| 全てのspan | //span |
| 親aの子span | a/span |
| aを祖先に持つspan | a//span |
| クラスに「aaa」を含むspan | span[contains(@&#x200b;class,'aaa')] |
| クラスに「aaa」と一致するspan | span[@&#x200b;class='aaa'] |
| クラスに「aaa」を含み、子にaを持つspan | span[contains(@&#x200b;class, 'aaa') and ./a] |
| 「aaa」というテキストを含むspan | span[contains(normalize-space(),'aaa')] |
| 「同階層（きょうだい）にいる2番目のspan | 〜/span[2] |
| 「括弧内に合致するspan（複数）」の2番目 | (〜/span)[2] |

_※spanは書き換える（指定したいタグ名）
※aaaは書き換える（任意のテキストやクラス名など）
※classは書き換える（タグ内の「=」の前にある属性名）
※順番を決める場合、丸括弧の始まりをどこにすればいいか気をつける_

指定したい要素（UI）は、先祖代々にわたって複数のタグを記載していく場合、一番右にあるタグ名（UI要素）になります。
指定したい要素：span[]の括弧内には、spanを絞り込むための別条件を追加できますが、「span[]」の捉え方は、日本語で言えば修飾語のように、「[]なspan」になります。

## 結論
例えば、2024年12月時点で、[スタンバイのトップ画面](https://jp.stanby.com/)の「検索」ボタンのxpathは、Chromeの開発者ツールからコピーすると以下になります。

- Copy full Xpath
  - /html/body/div[1]/div/main/div[1]/div/div[3]/div[1]/div/div/div[2]/button
- Copy Xpath
  - //*[@&#x200b;id="__nuxt"]/div/main/div[1]/div/div[3]/div[1]/div/div/div[2]/button

誰のことを必死に伝えたいのか、途中でよくわからなくなってきますね。あー、あのボタンの子ね、とはならないです。

現時点でのMagicPod上では、以下で選択可能です。（意訳付き）

- css=button.parts-search-button
  - クラス名が「parts-search-button」なbutton
- css=div.condition-decision>button
  - クラス名が「condition-decision」なdivの 子button
- xpath=//span[text()='検索']/parent::*
  - テキストが「検索」なspanタグの 親要素（タグ名*は任意）
- xpath=//div[contains(@&#x200b;class, 'search-form')]/div[2]/button[1]
  - クラス名に「search-form」を含むdivの 子供div（2番目）の 子供button

cssセレクターによる指定もオススメですが、xpathとの大きな違いは、指定するUIの子孫の条件で絞り込みができない（[]による絞り込みの一部が行えない）ことだと認識しています。
ちなみに、xpathが解読できれば、cssセレクターの指定もほぼ覚えることはないです。

あえて、己の力で作成するとしたら、以下で指定が可能です。
- //button[@&#x200b;class='parts-search-button']
  - クラス名が「parts-search-button」なbutton

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3617972/48ecb942-c3bc-6d7b-6d75-8ecd9bead51c.png)
※新しいロケータの追加は、「xpath」を選択/文字列を入力して、「OK」ボタンをクリックするだけの簡単な操作
※名前は、xpathを編集したことを示す方が、メンテしやすい
※複数人でシナリオ作成する用に、作成したxpathはスプレッドシート等にリストで共有した方が便利

なお、クラス名が重複する可能性を考えると、指定したいUIが属する大きなUIで囲ってから指定をすること（例では、検索ボックスのUI）で、汎用性が高まります。
- //div[contains(@&#x200b;class,'search-box')]//button[contains(@&#x200b;class,'parts-search-button')]
  - クラス名が「search-box」な祖先divの血筋にいる、クラス名が「parts-search-button」を含むbutton

ちなみに例に出しておいて、なんですが、ボックス内のテキスト「検索」や、クラス名「parts-search-button」は、大型のUI刷新以外で変わる可能性も低いので、自分で作成する必要はなく、MagicPodの提案そのままでOKです。
その判断ができるようになったことも、しっかりとした成長でしょうか。

## 最後に
数個のxpathの書き方を覚えれば、日常で使う8割方のxpathは読めるようになりますし、実現したい操作が行えないときに、さらにxpathについて調べてみるといいでしょう。
複雑な要素の操作（カレンダーなど）や、条件や順番で指定したいUIは、先ほどのリンク「新しいロケータを追加・編集する」にある変数を活用しxpathを使用することで、意図通り、かつ簡易に操作が行えるようになったりします。

なぜ、MagicPodというサービスが操作の再現にスコープするのではなく、キャプチャからのUI指定になったのか、想像してみることもできるようになると思います。

ちなみに作成したxpathが、どこにヒットするか否かを確かめたい時は、「Chrome> 開発者ツール> Elements」タブ内を「作成したxpathの文字列」で検索することで可能となります。