プログラミング初心者がアーキテクトっぽく語る

見苦しい記事も多数あるとは思いますが訂正しつつブログと共に成長していければと思います

SeleniumをPythonで使う

Seleniumとは

Web ScrapingやWeb UIのテストで利用するブラウザ操作自動化ツールです。

JavaPythonC#RubyJavaScript、Kotlinなどの言語で利用できます。

今回はPythonで利用します。


基本的にはテスト自動化ツール

基本的にはテスト自動化ツールと考えた方がよいです。

勘違いしてはいけないのは既存手動業務(Webサーバへの入力作業)を全自動化するためのツールとしては必ずしも最適ではないということです。

そのような複雑な自動化はWeb APIを使った方が確実ですし、安定性や性能面でも優れたものになるはずです。

Seleniumはきれいに動くこともあればきれいに動かないこともあります。

原因はWebページのDOM構造だったり、Selenium自体の仕様や問題だったりします。

単純なWeb Scrapingなら問題ないことが多いですが、はまるときは本当にはまるので注意してください。


準備

必要なものはchromedriverとseleniumです。

chromedriverはMacならhomebrewでインストールできます。

brew install chromedriver

次にseleniumをpip/pipenvでインストールしましょう。

pip install selenium

Hello World

とりあえず動かしてみましょう。

Selenium公式サイトにあるサンプルです。

import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.get("http://selenium.dev")
time.sleep(5)
driver.quit()

環境のセットアップが完了していればコピペで動きます。

実行するとChromeブラウザが起動し、公式サイトにアクセスし、5秒後にブラウザが終了します。


基本的な操作

以下に基本的な操作の方法を紹介します。

文字を取得

  • Chromeデベロッパツールを使ってテキストフィールドを一意に識別できる情報(Locator)を見つける
    • LocatorにはID、Class name、XPATHなどが使用できる
    • SeleniumのおすすめはID
  • find_elementメソッドでエレメントを取得
  • エレメント.textで文字を取得
message_element = driver.find_element(by=By.ID, value='message')
print(message_element.text)
  • 前提条件としてByをimportする必要がある
from selenium.webdriver.common.by import By

文字を入力

  • 先程と同じ方法で文字列入力フィールドのエレメントを取得
  • send_keysメソッドでテキストを送る
text_field_element = driver.find_element(by=By.ID, value='text')
text_field_element.send_keys('some text')

ボタンをクリック

  • 先程と同じ方法でボタンのエレメントを取得
  • clickメソッドで押す
button = self.browser.find_element(by=By.ID, value='button')
button.click()

エレメントが表示されるまで待つ

  • 特定のエレメントが表示されるまで待つことができる
  • ページの読み込み・表示が遅いときに有効
  • 下の例ではid=messageのエレメントが見つかるまで最大60秒間待つ
portal_name_node = WebDriverWait(driver, 60).until(
    EC.visibility_of_element_located((By.ID, 'message')))
  • 前提条件としてWebDriverWaitをimportする必要がある
from selenium.webdriver.support.ui import WebDriverWait

iframeへアクセス

  • iframeの中へアクセスする場合はiframeのエレメントを取得してswitch_toする
iframe = driver.find_element(by=By.TAG_NAME, value='iframe')
driver.switch_to.frame(iframe)

Shadow DOMへアクセス

  • Shadow DOMは他のDOMから独立したTreeを作成する技術
  • Shadow DOMにアクセスする場合はShadow DOMのエレメントを取得してshadow_rootを取得する
shadow_dom = driver.find_element(by=By.ID, value='foo')
root = shadow_dom.shadow_root

Python終了時にChromeを閉じない

options = Options()
options.add_experimental_option('detach', True)
driver = webdriver.Chrome(options=options)
  • 前提条件としてOptionsのimportが必要
from selenium.webdriver.chrome.options import Options

試験作成時の注意点

公式に色々と有益な情報があります。

https://www.selenium.dev/documentation/test_practices/

ここでは一部のみご紹介します。

Page Objectパターンを使う

  • DOMアクセス処理をテストコードから排除しPage Objectへ集中させる
  • DOM構造が変化した場合でもテストコードは影響を受けない

Fluent APIを使う

  • Page Objectのメソッドは次のPage Objectを返す
  • メソッドチェーンするときれいに見える
Fluent APIを使わない場合
signin_page.login(username, password)
home_page = HomePage()
home_page.go_to_profile_page()
profile = Profile()
profile.change_name('new name')
Fluent APIを使った場合
signin_page.login(username, password).go_to_profile_page().change_name('new name')

Seleniumでログインしない

  • テストが遅くなる
  • APISDKなどでログインすること

force-aloha-pageの苦労話

SalesforceのあるアプリがSeleniumで読めたり、読めなかったりと挙動が不安定でした。

色々と調査した結果、以下のことがわかりました。

  • force-aloha-pageタグがShadow DOM
  • Shadow DOMのすぐ下にiframeがある

iframeにはすぐ気づきましたがforce-aloha-pageがShadow DOMであることに気づけず時間を無駄にしました。

そこに気づけば対処はシンプルです。

  • force-aloha-pageタグのshadow_rootを取得
  • shadow_rootを利用してiframeのエレメントを取得
  • iframeのエレメントへswitch_to

ここまで進んだところでiframeの中が表示されるのにかなり時間がかかることがわかりました。

iframeにswtich_toした後にiframe内のエレメントにアクセスしようとすると例外がガンガン発生します。

苦肉の策としてiframe内のエレメントに最初にアクセスするところで数回Retryさせることにしました。

また、IDがLocatorとして使えないケースが多く、XPATHを多用することになりました。