Selenium + Chrome + Python = ラクしてカンタンに「UIテストの自動化」を！

# 前書き

回してみると何かと便利な「UIテストの自動化」。

回している人はガッツリ利用しているけど、
回していない人はまだまだで敬遠しがちなこともあるようで。。。
（準備が大変そう、とか）


# お伝えしたいこと

Selenium + Chrome + Python なら、
ラクしてカンタンに「UIテストの自動化」を実現できますよ！
ということをお伝えしようと記事を書いてみました。

学習コストをあまりかけずに「UIテストの自動化」を行いたい方は、ぜひご一読ください！


# Selenium を使おう！

では・・・とりあえず、使ってみましょう！

## Selenium を使うための４つのポイント

押さえるべき４つのポイントです。
ここを押さえるだけでも、割と色々な自動化ができるようになるはず！

- (1) ChromeのWebドライバーを入手しよう

- (2) PythonでブラウザのWebドライバーを参照しよう

- (3) 明示的な待機を行おう

- (4) XPathで要素を掴もう

それぞれポイントを見ていきます。


### (1) ChromeのWebドライバーを入手しよう

ChromeのWebドライバーは以下からダウンロードできます。
https://chromedriver.chromium.org/downloads

記事執筆時点での最新版は **78.0.3904.11** です。
更新日も **2019/9/12** と新しく、更新頻度の高さも伺えます。

ダウンロードしたら、お好みのディレクトリ配下にWebドライバーの実行ファイルを配置します。


### (2) PythonでブラウザのWebドライバーを参照しよう

以下はChromeのWebドライバーを参照して、
Googleを開くというHelloWorld的なコードです。
処理内容は以下の通りです。

- (1) Google Chrome で Google を開く

````python
from selenium import webdriver

# Webdriver定義 (各自DLして入手する必要あり, 環境に応じて書き換えてください)
driver = webdriver.Chrome("c:/chromedriver/chromedriver.exe")

# Chromeを開く
driver.get("https://www.google.co.jp/")
````

これだけでOK。シンプルです。

(ちなみにSeleniumのよいところは、メジャーな言語であれば大体利用できること。
 本記事はPythonですが、
 Java、Ruby、C#、PHPあたりでも利用できると思います。
 Pythonに拘らず、好みの言語で利用するのが吉かと！)


### (3) 明示的な待機を行おう

「UIテストの自動化」を考えるときに重要になるのは、
**「自動処理にページ描画が追い付いているか」**です。

例えば**「ボタンをクリックするテスト」**を走らせるとき、
**押したいボタンがブラウザに描画されていない**と、テストは失敗します。

以下、一例です。
処理内容は以下の通りです。

- (1) Google Chrome で Google を開く

- (2) 画面右上の「画像」リンクをクリックする

- (3) 画面を開いたまま3秒待つ

````python
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By

# Webdriver定義 (各自DLして入手する必要あり)
driver = webdriver.Chrome("c:/chromedriver/chromedriver.exe")

# Chromeを開く
driver.get("https://www.google.co.jp/")

# 画面右上の「画像」リンクをクリックする
element = driver.find_element_by_link_text("画像")
element.click()

driver.quit()
exit()
````

上記処理は一見問題なく動作しますが、
**「画面が描画しきれたか」を確認せずに「画像」のリンクを押しにいく**ため、
通信速度などの問題で画面描画が遅れた時には「画像」のリンクは見つからず、
エラーで処理が止まります。

こうした問題をフォローする為、
Selenium は「明示的な待機」をサポートしています。

「明示的な待機」とは、
**「操作したい要素が描画されるまでは処理を待機させる」**こと。
ブラウザへの要素の描画速度を極力考慮せずに処理を書くことができます。

以下、一例です。
処理内容は先ほどの内容を改善したものです。

- (1) Google Chrome で Google を開く

- (2) 明示的待機 30秒を定義

- (3) 画面右上の「画像」リンクをクリックする

- (4) 画面を開いたまま3秒待つ

````python
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import time

# Webdriver定義 (各自DLして入手する必要あり)
driver = webdriver.Chrome("c:/chromedriver/chromedriver.exe")
driver.get("https://www.google.co.jp/")

# 明示的待機 30秒
wait = WebDriverWait(driver, 30)

# 画面右上の「画像」リンクをクリックする
element = driver.find_element_by_link_text("画像")
element.click()
sleep(3)

driver.quit()
exit()
````

処理結果は一見同じです。
ただし「明示的待機 30秒」の定義を入れているので、
以下の点が異なります。

- Google を開いて「画像」リンクが表示されるまでMAX30秒待ってから、「画像」リンクをクリックする。

- 「画像」リンクが表示されれば、30秒も待たずにクリックする。

こうした対応は、描画処理や通信の遅れ等で、
次の要素（ボタンや入力項目）が画面に無いまま処理が走る、
といったトラブルを防ぐのに有効なアプローチです。


### (4) XPathで要素を掴もう

Seleniumでは様々な方法（ID, Name, タグ名, Class名等）で、
ページ内要素（ボタンや入力項目など）を掴んで、
そこに次の処理をあてていくことで自動処理を実現できます。

種類が多いと覚えることも増えそうですが、
私がお勧めしたい方法は「XPath」で要素を掴むこと。

殆どの要素は「XPath」で掴むことができますよ！
以下のフォームで「XPath」での掴み方をみていきます。

````HTML
<html>
  <body>
    <form id="test_fm" class="test_class">
      <input name="user_id" type="text" />
      <input name="passwd" type="password" />
      <input name="continue" type="submit" value="Login" />
      <input name="continue" type="button" value="Clear" />
    </form>
  </body>
<html>
````

上記のフォームを掴む場合は、以下のように書けばOK。どの書き方でも有効です。

````
# 絶対パスで掴む
test_fm = driver.find_element_by_xpath("/html/body/form[1]")

# HTMLの要素で掴む
test_fm = driver.find_element_by_xpath("//form[1]")

# IDで掴む
test_fm = driver.find_element_by_xpath("//form[@id='test_fm']")

# classで掴む
test_fm = driver.find_element_by_xpath("//form[@class='test_class']")
`````

上記のフォーム内入力項目１「user_id」を掴む場合は、以下のように書けばOK。どの書き方でも有効です。

````
# フォーム要素 ⇒ 入力項目の名前 と掘っていき掴む
user_id = driver.find_element_by_xpath("//form[input/@name='user_id']")

# フォーム要素 ⇒ 入力項目の1件目の項目 と掘っていき掴む
user_id = driver.find_element_by_xpath("//form[@id='test_fm']/input[1]")

# フォームは見ずに入力項目の名前で直接掴む
user_id = driver.find_element_by_xpath("//input[@name='user_id']")
````

このように、非常に自由度の高い書き方が可能です。
XPathで要素を掴むことで、ID定義があるページでも、ないページでも、
**テストしたいページのタグ構成に応じた要素の掴み方**ができるのです。

基本は以下のように理解しておけば大丈夫です。

````
driver.find_element_by_xpath("//第１検索項目[第２検索項目]")
````

XPathで要素を掴む際のより詳しい情報は、以下のマニュアルを参考にしてください。
https://kurozumi.github.io/selenium-python/locating-elements.html#locating-by-xpath


## まとめ 

以上の４点くらいをおさえておけば、
大半のWebサイトを、割とカンタンに自動化できるはずです。

Seleniumを使われたことがない方はぜひこの機会に、
ラクしてカンタンに「UIテストの自動化」を実現してもらえれば！


## オマケ：サンプルコード

本記事の内容を参考に、
ChromeのWebドライバーを用いたサンプルコードを書いてみました。
処理内容は以下の通りで、なんともしょうもないことをやっております。
(記事投稿時点では動作しますが、Googleのデザイン等が変われば動作しなくなる可能性ありです。)

- (1) Google Chrome で Google を開く

- (2) 明示的待機 30秒を定義

- (3) 検索ワード「QIITA」で検索

- (4) 検索後に Google を開く

- (5) 検索ワード「キター」で検索

- (6) 検索後に Google を開く

- (7) 検索ワード「今北産業」で検索

- (8) 検索後に 画面内 の「動画」タブを選択

- (9) 「動画」タブ内のスクリーンショットを撮って処理終了


記事に入れていなかった要素として、
**スクリーンショットの取得処理**も入れ込みました。
これを上手く使えば、人の目無しでの完全自動検証も実現できるはず！


````python
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import time
import os

# Webdriver定義 (各自DLして入手する必要あり)
driver = webdriver.Chrome("c:/chromedriver/chromedriver.exe")
wait = WebDriverWait(driver, 30)

wordlist = []
wordlist.append("QIITA")
wordlist.append("キター")
wordlist.append("今北産業")

for word in wordlist:
    driver.get("https://www.google.co.jp/")

    element = wait.until(expected_conditions.element_to_be_clickable(
        (By.XPATH, "//input[@class='gLFyf gsfi']")))
    element.send_keys(word)
    sleep(3)

    element = wait.until(expected_conditions.element_to_be_clickable(
        (By.XPATH, "//input[@class='gNO89b']")))
    element.click()
    sleep(3)

element = wait.until(expected_conditions.element_to_be_clickable(
    (By.XPATH, "//div[@class='hdtb-mitem hdtb-imb'][3]")))
element.click()
sleep(3)

FILENAME = os.path.join(os.path.dirname(
    os.path.abspath(__file__)), "screen.png")
driver.save_screenshot(FILENAME)
sleep(10)

driver.quit()
exit()
````

処理を色々書き換えると、あんなことやこんなこともできて楽しいです。
ページ内の要素を掴むためのコツとかも見えてくるかと思います。

ぜひ、Seleniumを試してみてください！