E2E自動テストを実装するためのロケーターの基礎

## はじめに
おしごとでWebのE2Eテスト自動化をやっているものの
正規表現然り、少し使ってないと書き方を忘れてしまうため、書き残しておきます。

ロケーターとは何かを知る上では次のスライドが大変わかりやすいです。
[ロケーターを学んでテスト自動化上級者を目指そう](https://speakerdeck.com/nozomiito/roketawoxue-ndetesutozi-dong-hua-shang-ji-zhe-womu-zhi-sou)[^1]

ロケーターの選び方にも色々な考え方があり、どれが最善かは組織やテスト対象システムの作りによって変わってきます。
全てのテスト対象システムがはじめからE2E自動テストの実装に適した状態であるとは限らず、
リファクタリングを行わないと、自動テストの実装が困難な場合も出てくると思います。
一方で、機能追加や機能拡張の要望が強く、なかなかリファクタリングまで手が回らないという開発組織もあり、
やむを得なく複雑なロケーターを指定せざるを得ないという場面も多いのではないでしょうか。
E2Eテストの恩恵を理解してもらい、リファクタリングを進めてもらう努力をしつつ、
この記事を通じて、今ある状態でE2Eテストを実装するための技術を身につけるための一助になれば幸いです。

## ロケーター一覧
CSSセレクターとXPathについて紹介していきます。
- CSSセレクター
元々はCSSでデザインを指定するときに、この要素はこういう色にしてね、こういう配置してね、と指定するときに使われるものです。

- XPath
Webページの情報を収集するクローラーなどでよく使われるものです。

### 要素名で指定する
要素名のみを指定して要素を特定するケースは少ないと思いますが、ルールを理解する上で重要なので念のため。
```html
<input name="password">
```

|CSSセレクター|XPath|
|-|-|
|`input`|`//input`|

なお、任意の要素名を選択したい場合は`*`を指定します。

### id属性で指定する
```html
<div id="main">
</div>
```

|CSSセレクター|XPath|
|-|-|
|`#main`|`//*[@id='main']`|

### class属性で指定する
```html
<button class="primaryButton">次へ</button>
<button class="secondaryButton smallText backButton--74XTSMX">戻る</button>
```

||CSSセレクター|XPath|
|-|-|-|
|class属性で指定する|`.primaryButton`|`//*[@class='primaryButton']`|
|要素名とclass属性の組み合わせで指定する|`button.primaryButton`|`//button[@class='primaryButton']`|
|複数のclassが指定されている場合|`.secondaryButton` <br>※複数指定されていても同様の指定ができます)|`//button[contains(@class, 'secondaryButton')]`<br>※`secondaryButton`が含まれるか？を`contains()`で表現します|
|部分一致で指定する場合 (`backButton--`を含む)|button[class*='backButton--']|`//button[contains(@class, 'backButton--')]`|
|先頭一致で指定する場合 (`backButton--`で始まる)|`button[class^='backButton--']`|-|
|後方一致で指定する場合 (`Button`で終わる)|`button[class$='Button']`|-|

部分一致は、class名の末尾が動的に変化する可能性のある場合に使用します。

### id, class以外の属性で指定する
```html
<div data-testid="password-area">
  <input name="password"　value="パスワード">
</div>
```

||CSSセレクター|XPath|
|-|-|-|
|name属性で指定|`input[name='password']`<br>|`//input[@name='password']`|
|value属性で指定|`input[value='パスワード']`|`//input[@value='パスワード']`|
|data-testid属性で指定|`div[data-testid='password-area']`|`//div[@data-testid='password-area']`|

規則性が見えてきたと思いますが、他の属性を指定する場合は、`name`や`value`属性の部分を変更すれば良いです。

### インナーテキストで指定する
```html
<div>
  <span>重要</span>
  <span>あなたのアカウント名はhogehogeです</span>
</div>
```

||CSSセレクター|XPath|
|-|-|-|
|完全一致|-|`//span[text()='重要']`|
|部分一致|-|`//span[contains(text(), 'アカウント名は')]`|
|〜で始まる|-|`//span[starts-with(text(), 'あなたのアカウント名は')]`|


時々、上記の指定では取得できない要素が出現します。デベロッパーツールでElementsを確認するとこんな感じになっている要素です。
```html
<td>
    "山田"
    "太郎"
</td>
```

この場合、このような書き方をすると要素が取得できます。

|CSSセレクター|XPath|
|-|-|
|-|`//td[.='山田太郎']`|

また、インナーテキスト内にタグが含まれるようなケースです。

```html
<td>
    "氏名"
    <span>":"</span>
</td>
```

この場合、このような書き方をすると要素が取得できます。

|CSSセレクター|XPath|
|-|-|
|-|`//td[.='氏名:']`|

### 階層構造を利用して指定する
```html
<div>
  <span>
    <input> 
  </span>
</div>
```

||CSSセレクター|XPath|
|-|-|-|
|子を指定する|`div > span > input`|`//div/span/input`|
|子孫を指定する（子要素を明示しない）|`div input`|`//div//input`|

これを見てお気づきかと思いますが、おまじないのように先頭につけていた`//`は階層を省略していることを意味します。
ルートから省略せずに書くと次のようになりますが、E2Eテストを実装する際に指定するロケーターとして対象の要素を選択するには冗長なので、
ロケーターとして指定する際には先頭のパスは省略することがほとんどです。

```html
<html>
  <body>
    <div>
      ...
    </div>
  </body>
</html>
```
`/html/body/div/...`

### 兄弟関係で指定する
```html
<ul>
  <li>りんご</li>
  <li>いちご</li>
  <li>みかん</li>
  <span>文字列</span>
</ul>
```

||CSSセレクター|XPath|
|-|-|-|
|指定した子要素の順序関係を利用して指定|`ul > li:nth-of-type(2)`<br>**→ `<li>いちご</li>`が選択される**|`//ul/li[2]`<br>**→ `<li>いちご</li>`が選択される**|
|すべての子要素の中の順序関係を利用して指定|`ul > li:nth-child(4)`<br>**→ `<span>文字列</span>が選択される**|-|

### 複数条件を指定する
XPathでは複数条件も指定することができます。
単独の条件では要素を一意に特定できない場合に利用します。
```html
<div class="footer--7rfdx">お問合せはこちら</div>
```

|CSSセレクター|XPath|
|-|-|
|-|`//div[contains(@class, 'footer' and text()='お問合せはこちら')]`|

andもあるなら、orもありますが、E2Eテストのロケーターとしてはあまり使わないかもしれません。

### パスで指定する

XPath限定ですが、階層を戻ったり、たどったりすることができます。
```html
<div>
    <h2>アカウント情報入力</h2>
    <div class="1">
        <label id="name">氏名</label>
    </div>
    <div>
        <input name="name">
    </div>
    <div class="2">
        <label id="address">住所</label>
    </div>
    <div>
        <input name="address">
    </div>
    <div class="3">
        <label id="phonenumber">電話番号</label>
    </div>
    <div>
        <input name="phonenumber">
    </div>
</div>
```

||CSSセレクター|XPath|
|-|-|-|
|親の要素を選択|-|`//label[@id='address']/..`<br>**→ `<div class="2">`が選択される**|
|親の要素を選択|-|`//label[@id='address']/parent::div`<br>**→ `<div class="2">`が選択される**|
|同じ階層にあり、かつ前に出てくる兄弟ノードの集合(preceding-sibling)を用いた選択の方法|-|`//label[@id='address']/../preceding-sibling::h2`<br>**→ `<h2>`が選択される**|
|同じ階層にあり、かつ後に出てくる兄弟ノードの集合(following-sibling)を用いた選択の方法|-|`//label[@id='address']/parent::div/following-sibling::div[1]/input`<br>**→ `<input name="address">`が選択される**|

今回はわかりやすいように選択される要素にclass属性やname属性を書いていますが、
実際には単独で指定しても要素を特定しにくいような場合に上記の記述方法が使えます。
XPathについてもっと詳しく知りたい場合は、参考文献[^2]を見てみると良いでしょう。

## おまけ
記述したロケーターが正しいことを確認するために、毎回最初からテストスクリプトを実行していると、膨大な時間がかかってしまいます。
手元のChromeでF12キーを押してデベロッパーツールを開き、Consoleタブを開いてロケーターを検証してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549722/46bf840d-8b49-e199-1811-ed58dab12090.png)

### CSSセレクターの検証
`document.querySelector("{検証したいCSSセレクター}")`

例)`document.querySelector("input[name='password']")`


### XPathの検証
`$x("{検証したいXPath}")`

例) `$x("//input[@name='password']")`

## 参考文献
記事を書くにあたり、Selenium実践入門[^3]の付録も大いに参考になりました。

[^1]: [ロケーターを学んでテスト自動化上級者を目指そう](https://speakerdeck.com/nozomiito/roketawoxue-ndetesutozi-dong-hua-shang-ji-zhe-womu-zhi-sou). JaSST'23 Tokyo
[^2]: [便利なXPathまとめ](https://techblog.zozo.com/entry/xpath). ZOZO TECH BLOG
[^3]: 伊藤 望, 戸田 広, 沖田 邦夫, 宮田 淳平, 長谷川 淳, 清水 直樹 & Vishal Banthia. Selenium実践入門 自動化による継続的なブラウザテスト. WEB+DB PRESS plus