のーずいだんぷ

主に自分用メモですが、もしかしたら誰かの役に立つかもしれません

Lambdaでseleniumを使ってフォームテストを自動化してみた

はじめに

サイトの問い合わせフォームの調子が時折悪くなるため、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()

基本的に上記では

  1. driverインスタンスを取得
  2. driver.find_element_by_name()で各DOM要素を取得
  3. send_keys()で要素を入力
  4. 2〜3を各フォームに対して実行
  5. driver.find_element_by_name("submit").click()でフォームの送信

のような感じである。

ちなみに、要素の取得にはname属性を使用しているが、これはXPATHとかも使うことができる。

Lambdaデプロイパッケージの準備

lambaの使用経験がある人ならご存知だろうが、lambdaはaws-sdkと組み込みモジュール以外のライブラリを使用するには、

  1. 予めLambda Layerを用意しておく
  2. デプロイパッケージにまとめる

上記2点の方法がある。今回は2の方法で対処しようと思う。

2の場合、ハマりどころがいくつかあるのでこれは以下の過去記事を参照されたい。

www.nooozui.com

seleniumとheadless-chromeをインストール

まず先にheadless-chromeを入手する。 どうもlambdaで実行するための専用のheadless-chromeがあるのでそれを使用した方が良いそうだ。 (通常のheadless-chromeでは試していない。)

github.com

今回はこの最新のリリースの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を選択した。

sites.google.com

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

でデプロイ完了となる。

参考

qiita.com