スクリプトのお勉強 技術

Selenium + Python によるアップロードアプリの動作確認プログラム作成

投稿日:2019年8月19日 更新日:

私の周りでは、なぜか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

-スクリプトのお勉強, 技術

執筆者:

関連記事

Python3 – VCR.py でネットワーク系テストを簡単に作成する

1. 始めに python3で実装すると、モックテストをしたくなります。モックを使って、比較的簡単にテストできるからです。 問題はネットワーク系テスト モックテストで問題になるのは、外部に依存するテス …

CentOS7 + Django2.2 + uwsgi + nginx 連携方法

Djangoとnginx連携方法 以前はApache経由で連携しましたが、今回はnginxと連携する方法を記述します。 環境 設定した環境は以下です。 OS: CentOS7Python3.6.8 ( …

Ubuntu 20.04のMySQL8.0.22でrootパスワードをリセットする

小ネタです。 休みなので 久しぶりにローカル環境のUbuntuでMySQLにアクセスしようとして、パスワードを見事に忘れたことに気づきました。 ubuntu 20.04なので、以下の手順でパスワードの …

言語別ログイン機能パスワード保存処理方針

ちょっと前に、ログイン機能を作成した際、パスワードを暗号化するか、という議論を目にしたことがありました。 昔だと、「パスワードを暗号化しない」方で実装していましたが、最近はセキュリティが当たり前になっ …

Mojolicious XML-RPC Pluginの開発(1)

はじめに 最近、お仕事関連で、perlとWebアプリケーションを触っています。 両方一度にお勉強、ということで、perlのWAF(Webアプリケーションフレームワーク)を調べてみようと思いました。 こ …