私の周りでは、なぜかSeleniumが流行っている模様です。
私自身は、Webアプリ的なのも作ってますが、あまり使ってなかったので、使ってみようと思います。
前回作成した、Djangoのupload_formを使用して、動作確認をするプログラムを作成しようと思います。
Seleniumとは
Seleniumとは、Webアプリケーションをテストするための移植可能な
フレームワークです(from ウィキペディア)。
ブラウザの操作手順をプログラミングするためのフレームワークと言えるでしょう。
各ブラウザ(ChromeとかFirefoxとか)がする処理、例えば、URLにアクセスして、ボタンをクリックするとか、を抽象化して処理できるよう、Python等で、その処理を書くことができます。
Seleniumの要素技術
「Selenium」と一言で言っても、何から構成されている分かりにくいと思います。
googleで検索すると、昔のSeleniumも出てきて、さらに分かりにくさが増してる気がします。
2019年8月現在、Seleniumは、以下のように区分け出来るようです。
- Selenium WebDriver
- Selenium Grid/Selenium Remote Control
- Selenium IDE
Selenium WebDriver
WebDriverとは、ブラウザを操作するプログラムです。WebDriver’s wire protocolなるプロトコルで、ブラウザと通信します。
具体的には、WebDriver自身もプログラムで、内部で起動してHTTPで通信しているようです。
具体的には、http://localhost:9195で通信しているようです。
そしてこのWebDriverは、各ブラウザ、というよりバージョンごとの実装があります。
Chrome本体とバージョンを合わせる必要があります。
ChromeはChromeDriverというプログラムになります。
処理イメージは、以下のような感じです。
Selenium API(ライブラリ) <->(HTTP通信) WebDriver(ChromeDriver) -> Chrome本体
上記の「Selenium API」とは、各言語での「Selenium」を操作するライブラリを指しています。
API自身はブラウザが異なっても同じです。
一番出てくる意味の「Selenium」とは、ブラウザを直接操作するのではなく、WebDriver経由で通信することで、
複数のブラウザを同一のAPIを呼び出すことで制御できるライブラリを指しています。
Selenium Grid/Selenium Remote Control
上記でAPIと、WebDriverがHTTP通信しているという話をしましたが、WebDriverがローカルホストではなく、外部ホストにあるイメージです。
今回は使用しませんので詳細は割愛します。
Selenium IDE
ChromeやFirefoxのプラグインとして動作する、Web動作の記録再生を自動的に行うアプリケーションのことです。
Selenium IDEから、Web上の操作を保存したり、再生して再現させたりできるそうです。
ただ、旧バージョンからすると、機能が少ないとのこと。
これも今回は使用しませんので説明は割愛します。
動作環境
Seleniumを動作させた環境は以下です。
OS: ubuntu 18.0.4
Python: 3.6.
Selenium API(python) と WebDriverのインストール
ダウンロードは公式サイトから行えます。
### ダウンロードしたファイルを ### カレントディレクトリ下のweb_driverに展開 $ mkdir web_driver $ cd web_driver $ wget https://chromedriver.storage.googleapis.com/76.0.3809.68/chromedriver_linux64.zip $ unzip chromedriver_linux64.zip $ cd .. $ pipenv install selenium
selenium_test.py の起動
動作確認用に、selenium_test.pyを以下のようにしました。
import os import logging from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.chrome.options import Options import sys DRIVER_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'web_driver', 'chromedriver')) logger = logging.getLogger(__name__) def push_upload(driver): # 操作するページを開く driver.get('http://localhost:18889/upload_form') # アップロードボダンを押下して画像をアップロード(id: id_file) driver.find_element_by_id('id_file').send_keys(os.getcwd() +"/AKANE20160312012313_TP_V.jpg") # 「送信」ボタン押下 driver.find_element_by_id("upload_form").submit() driver.implicitly_wait(10) return driver.page_source def main(): # Selenium settings options = Options() # ヘッドレス(画面を表示しない) options.add_argument('headless') options.add_argument('window-size=800x600') # 操作するブラウザを開く driver = webdriver.Chrome(DRIVER_PATH, chrome_options=options) html = '' try: html = push_upload(driver) except Exception as e: logger.exception(e) driver.quit() # 画像が表示されるか確認 if '<img src="/media/mosaic_' in html: print("OK") else: print("NG") if __name__ == '__main__': main()
以下で起動します。
$ pipenv run python3 selenium_test.py
プログラムの中心は以下のところです。
# 操作するページを開く driver.get('http://localhost:18889/upload_form') # アップロードボダンを押下して画像をアップロード(id: id_file) driver.find_element_by_id('id_file').send_keys(os.getcwd()+"/AKANE20160312012313_TP_V.jpg") # 「送信」ボタン押下 driver.find_element_by_id("upload_form").submit()
要するに、id=”id_file”の要素を取り出して、「アップロード」ボタンを押下して画像を送信し
submitしています。
動作確認としては、正常に以下のようなHTMLが返ってきているか、imgタグが存在するかどうか
チェックしています。
<img src="/media/mosaic_44dee3eda667426ba0d5993fb1100013.jpg">
なお、driver.quit()を呼び出さないと、chromedriverが停止しないので注意してください。
停止しないと、chromeが増加していきますので、ある意味メモリリークに近い状況になります。
発生したエラー
実装途中で発生したエラーを記録しておきます。
以下のエラーは、結局ブラウザ本体を起動できないというエラーです。
unknown error: DevToolsActivePort file doesn't exist)
以下のエラーはchromedriverとchrome本体のバージョンが合わないときに出やすいようです。
Traceback (most recent call last): File "selenium_test.py", line 30, in <module> main() File "selenium_test.py", line 7, in main driver = webdriver.Chrome("./web_driver/chromedriver") File "/home/tanino/script-plactice/practice_the_script/python/django/.venv/lib/python3.6/site-packages/selenium/webdriver/chrome/webdriver.py", line 81, in __init__ desired_capabilities=desired_capabilities) File "/home/tanino/script-plactice/practice_the_script/python/django/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 157, in __init__ self.start_session(capabilities, browser_profile) File "/home/tanino/script-plactice/practice_the_script/python/django/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 252, in start_session response = self.execute(Command.NEW_SESSION, parameters) File "/home/tanino/script-plactice/practice_the_script/python/django/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute self.error_handler.check_response(response) File "/home/tanino/script-plactice/practice_the_script/python/django/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally (unknown error: DevToolsActivePort file doesn't exist) (The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)
以下のエラーもchromedriverとchrome本体のバージョンが合わないときに出やすいようです。
selenium.common.exceptions.WebDriverException: Message: unknown error: Devtools port number file contents <45109> were in an unexpected format
詳細は以下の通りです。
tanino@ubuntu:~/script-plactice/practice_the_script/python/django/selenium_test$ pipenv run python3 selenium_test.py Traceback (most recent call last): File "selenium_test.py", line 58, in <module> main() File "selenium_test.py", line 32, in main driver = webdriver.Chrome(DRIVER_PATH, chrome_options=options) File "/home/tanino/script-plactice/practice_the_script/python/django/selenium_test/.venv/lib/python3.6/site-packages/selenium/webdriver/chrome/webdriver.py", line 81, in __init__ desired_capabilities=desired_capabilities) File "/home/tanino/script-plactice/practice_the_script/python/django/selenium_test/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 157, in __init__ self.start_session(capabilities, browser_profile) File "/home/tanino/script-plactice/practice_the_script/python/django/selenium_test/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 252, in start_session response = self.execute(Command.NEW_SESSION, parameters) File "/home/tanino/script-plactice/practice_the_script/python/django/selenium_test/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute self.error_handler.check_response(response) File "/home/tanino/script-plactice/practice_the_script/python/django/selenium_test/.venv/lib/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.WebDriverException: Message: unknown error: Devtools port number file contents <45109> were in an unexpected format
おわりに
アップロードだけだと数行で処理できるぐらい、Selenium自体は簡単でした。
ただ、手動処理を一つ一つ実装していくとすると、結構な手間になると思います。
HTMLの構造を変えた場合、Seleniumの実装も変えざる得ないので、いろいろ苦労しそうです。
そんな感じで新しい技術がまた生まれるのでしょう。
参考
- SeleniumのPythonバインディング API
https://selenium-python.readthedocs.io/index.html - Selenium for Python API一覧
https://www.seleniumqref.com/api/webdriver_abc_python.html