はじめに
サイトの問い合わせフォームの調子が時折悪くなるため、seleniumとheadless-chromeを使用して、フォームの動作確認スクリプトを自動化したかった。
インフラにはAWS Lambdaを使用したが、seleniumに関するエラーが色々出たためちょっと苦労した。
とりあえず動けばOKがゴールだったのでコードとかはかなり雑に書いているのでポイントだけ理解いただきたい。
ソースコード
一部変更しているが、ざっくり以下のようなコードで実行している。
# handler.py import sys import time from datetime import datetime sys.path.append('/var/task/pkg') from selenium import webdriver from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By def lambda_handler(event, context): try: url = # フォームのurl submit = # 送信ボタンのname属性 first_name = # 名前フォームのname属性 last_name = # 名字フォームのname属性 company = #会社フォームのname属性 belongs = #部署・所属フォームのname属性 e_mail = #メールアドレスフォームのname属性 text_area = #自由入力欄のアドレスフォームのname属性 options = webdriver.ChromeOptions() options.binary_location = "./pkg/bin/headless-chromium" options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument("--single-process") options.add_argument('--disable-dev-shm-usage') driver = webdriver.Chrome(executable_path="./pkg/chromedriver_binary/chromedriver", options=options) driver.get(url) # 送信ボタンのロードまで待機(20秒以上でException発生) WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.NAME, submit))) # フォーム要素取得&値をフォームへ入力 ele_lastname = driver.find_element_by_name(last_name) ele_lastname.send_keys("hoge") ele_firstname = driver.find_element_by_name(first_name) ele_firstname.send_keys("huga") ele_company = driver.find_element_by_name(company) ele_company.send_keys("hogehoge.inc") ele_belongs = driver.find_element_by_name(belongs) ele_belongs.send_keys("開発部") ele_mail = driver.find_element_by_name(e_mail) ele_mail.send_keys("example@yahoo.co.jp") ele_form = driver.find_element_by_name(text_area) ele_form.send_keys("""実行日時:{}\nこれはseleniumによる自動テストです。""".format(datetime.now().ctime())) # 送信ボタン押下 driver.find_element_by_name("wpforms[submit]").click() except Exception: raise finally: print("end") driver.close() driver.quit()
基本的に上記では
- driverインスタンスを取得
driver.find_element_by_name()
で各DOM要素を取得send_keys()
で要素を入力- 2〜3を各フォームに対して実行
driver.find_element_by_name("submit").click()
でフォームの送信
のような感じである。
ちなみに、要素の取得にはname属性
を使用しているが、これはXPATHとかも使うことができる。
Lambdaデプロイパッケージの準備
lambaの使用経験がある人ならご存知だろうが、lambdaはaws-sdkと組み込みモジュール以外のライブラリを使用するには、
- 予めLambda Layerを用意しておく
- デプロイパッケージにまとめる
上記2点の方法がある。今回は2の方法で対処しようと思う。
2の場合、ハマりどころがいくつかあるのでこれは以下の過去記事を参照されたい。
seleniumとheadless-chromeをインストール
まず先にheadless-chromeを入手する。 どうもlambdaで実行するための専用のheadless-chromeがあるのでそれを使用した方が良いそうだ。 (通常のheadless-chromeでは試していない。)
今回はこの最新のリリースのstableをinstallした。install後、デプロイのためbin/
に移動している。
rm -rf bin wget https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-55/stable-headless-chromium-amazonlinux-2017-03.zip mkdir bin unzip stable-headless-chromium-amazonlinux-2017-03.zip rm stable-headless-chromium-amazonlinux-2017-03.zip mv headless-chromium bin/
seleniumuについては通常通り、pip
でインストールすればよいが、バージョンには注意する必要がある。
selenium-webDriberによって操作するが、これはchromeのバージョンに対応があるためである。
今回はchromium 69.0.3497.81
を使用してる事となるので、seleniumは以下サイトで対応を確認し、2.43
を選択した。
2.44
も対応しているのだが、オプションが関係しているのかchrome not reachable
が発生するため2.43としている。
なお、バージョンが異なる場合、
Message: session not created: This version of ChromeDriver only supports Chrome version 75
の様なエラーが出力されるので、その場合はchromeのバージョンとwebDriverのバージョンが対応しているか確認したい。
あとpipでインストールする場合、amazon-linuxOS上でビルドしたものでないとlambdaでは使用できないので注意したい。
最後に、これらの実行ファイルのパスについてdriverインスタンスの生成時に指定の必要があり、ここで一旦ソースコードの説明に戻る。
該当箇所は以下の場所である。
options.binary_location
では先程インストールしたlambda用のchromeの場所を指示する。
options = webdriver.ChromeOptions()
options.binary_location = "./pkg/bin/headless-chromium"
webdriver.Chrome
の引数executable_path
には、seleniumをインストールした際に一緒にインストールされるchromedriver
の実行ファイルの場所を示す必要がある。
driver = webdriver.Chrome(executable_path="./pkg/chromedriver_binary/chromedriver", options=options)
lambdaへデプロイ
パッケージ構成
今回は以下のようなパッケージを用意した。 おそらく関係ないライブラリも一部混ざっている。
$ tree -L 2 . ├── pkg │ ├── __init__.py │ ├── bin | └── headless-chromium │ ├── chromedriver_binary | └── chromedriver │ ├── chromedriver_binary-2.43.0.dist-info │ ├── easy_install.py │ ├── get │ ├── get-2019.4.13.dist-info │ ├── handler.py │ ├── pkg_resources │ ├── post │ ├── post-2019.4.13.dist-info │ ├── public │ ├── public-2019.4.13.dist-info │ ├── query_string │ ├── query_string-2019.4.13.dist-info │ ├── request │ ├── request-2019.4.13.dist-info │ ├── selenium │ ├── selenium-3.141.0.dist-info │ ├── setuptools │ ├── setuptools-42.0.2.dist-info │ ├── urllib3 │ └── urllib3-1.25.6.dist-info └── serverless.yml
Lambdaへデプロイ
デプロイはServerless-Frameworkを使用した。
日時で確認したかったので、CloudWatch eventで日時実行するような連携設定をしている。
service: form-check provider: name: aws runtime: python3.7 stage: dev region: ${opt:region,'ap-northeast-1'} profile: default functions: checkform: handler: pkg/handler.lambda_handler name: check-form description: selenium e2e-test runtime: python3.7 memorySize: 512 timeout: 120 tracing: False role: // cloudwatchlogsへのログ出力権限を持つIamRoleのarn events: - schedule: cron(30 0 * * ? *) package: include: - pkg
あとは
sls deploy
でデプロイ完了となる。