見出し画像

【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も試してみたいと思います。

参考情報

https://stackoverflow.com/questions/76906082/how-to-test-a-streamlit-application-with-selenium-and-pytest-testing-framework


Header Photo by Unsplash Chris Liverani


この記事が気に入ったらサポートをしてみませんか?