【Python】StreamlitのE2Eテスト、どうやる?
Streamlitで作成したアプリケーションのE2Eテストってどうしてますか?
最近Streamlitのテストを書く機会があったので、調べたことをまとめました。
テスト用フレームワークの比較
StreamlitアプリケーションのE2Eテスト実装の選択肢としては以下が挙げられます。
Streamlit AppTest
Selenium
SeleniumBase
Playwright
Streamlit AppTest
Streamlit公式のテストライブラリ。
簡単な動作確認であれば、これが一番シンプルで高速。
ただし、細かいことをやろうとすると機能が不十分な印象。Streamlitのコンポーネント単位での操作になるため、idやclassで要素を指定できないのが不便。
テストのサンプル
App testing - Streamlit Docs
from streamlit.testing.v1 import AppTest
at = AppTest.from_file("streamlit_app.py")
at.secrets["WORD"] = "Foobar"
at.run()
assert not at.exception
at.text_input("word").input("Bazbat").run()
assert at.warning[0].value == "Try again."
Selenium
Seleniumはよく動作が重いとか挙動が安定しないとか言われますが、なんだかんだで機能の充実度や使いやすさには一日の長があると思います。
SeleniumBase
今回調査する中で知りました。
Seleniumをベースにしたテスト用フレームワークで、素のSeleniumよりも簡潔にテストが書けます。後述しますが、今回私はこちらのSeleniumBaseを採用しました。
Playwright
上記のSeleniumBaseで満足したのと、Playwrightは(Pythonをサポートはしているものの)JavaScript/TypeScript向けな印象のフレームワークなので、今回は試しませんでした。なので省略。
Selenium vs SeleniumBase
まずはSeleniumを使ったテストのサンプルから。
import subprocess
import time
import pytest
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
@pytest.fixture(scope="session")
def driver():
# Start the Streamlit application as a separate process
app_process = subprocess.Popen(
["streamlit", "run", "app/Home.py"]
)
driver = webdriver.Chrome()
driver.implicitly_wait(3) # ★
yield driver
driver.quit()
# Stop the Streamlit application process after the test
app_process.terminate()
app_process.wait()
def test_home_title(driver):
driver.get("http://localhost:8501")
wait = WebDriverWait(driver, 3)
wait.until(EC.presence_of_element_located((By.TAG_NAME, "h1"))) # ★
title_element = driver.find_element(By.TAG_NAME, "h1")
assert driver.title == "Home"
assert title_element.text == "Welcome to My Streamlit App"
フィクスチャを使ってテストケースごとにStreamlitアプリケーションを起動&停止するようにします。
DOM要素が現れるまでの暗黙的待機(implicitly_wait)を設定することで、エラーを防ぎつつ(Sleepを使った場合のような)無駄な待機時間を無くすことができます。
またDOM要素の変化をチェックしたい場合は、WebDriverWaitを使って明示的待機することで待機時間を制御可能です。
では、SeleniumBaseを使うとどうなるか見てみます。
import subprocess
from seleniumbase import BaseCase
class PageContentTest(BaseCase):
@classmethod
def setUpClass(cls) -> None:
cls.app_process = subprocess.Popen(["streamlit", "run", "app/Home.py"])
def test_home_page(self) -> None:
self.open("http://localhost:8501")
self.assert_title("Home")
# Assert the headers
self.assert_text("Welcome to My Streamlit App")
self.assert_text("Instructions")
self.assert_text("Features")
# Assert the info element
self.assert_element("div.stAlert")
@classmethod
def tearDownClass(cls) -> None:
cls.app_process.terminate()
cls.app_process.wait()
webdriverの準備や、明示的/暗黙的待機の設定も自動で行われるため不要。
BaseCaseクラスにページの操作や検証用のメソッドが用意されています。
また、その他機能としてレポートやダッシュボード、DEMOモードなども用意されています。詳細はドキュメントをご参照ください。
以上を踏まえて、私はSeleniumBaseを採用することにしました。
手元のアプリケーションのテストを一通り書いてみて、特に不都合なく実装することができました。
まとめ
というわけで、StreamlitのE2Eテストに使えるフレームワークの比較とSeleniumBaseの紹介を行いました。機会があればplaywrightも試してみたいと思います。
参考情報
Header Photo by Unsplash Chris Liverani