Testing apps with AppTest

Streamlit · Advanced · 12 min

Automated tests catch broken widgets and logic before users do, without clicking through the app by hand.

streamlit.testing.v1.AppTest runs an app script without a browser and lets you inspect and interact with its elements:

from streamlit.testing.v1 import AppTest
at = AppTest.from_file("app.py").run()

from_string and from_function create apps from code directly, handy for small tests. After .run():

  • Elements are accessed by type, as lists in order of appearance: at.title[0].value, at.markdown, at.metric, at.dataframe, at.error, at.exception.
  • Widgets are accessed the same way or by key: at.slider[0], at.text_input(key="name").
  • Interact, then run again: at.slider[0].set_value(5).run(), at.button[0].click().run(), at.text_input[0].input("Ada").run(), at.selectbox[0].select("pro").run().
  • at.session_state["count"] reads or presets state; at.secrets["api_key"] = "test" provides secrets.
  • at.switch_page("pages/admin.py") tests multipage apps.

Assert that no exception occurred with assert not at.exception. Tests run in a fresh session each time, so they are isolated and fast enough for CI with pytest.

AppTest does not render HTML or JavaScript, so it cannot test visual layout or custom components; for that, use browser tests (Playwright). Keep business logic in plain functions with their own unit tests and use AppTest for the wiring.

Example

from streamlit.testing.v1 import AppTest

def app():                              # the app under test
    import streamlit as st
    price = st.number_input("Price", value=10.0, key="price")
    qty = st.slider("Qty", 1, 10, 1)
    if qty > 5:
        st.warning("Bulk order")
    st.metric("Total", f"{price * qty:.2f}")

def test_total_and_warning():
    at = AppTest.from_function(app).run()
    assert at.metric[0].value == "10.00"
    assert len(at.warning) == 0
    at.slider[0].set_value(6).run()
    at.number_input(key="price").set_value(2.5).run()
    assert at.metric[0].value == "15.00"
    assert at.warning[0].value == "Bulk order"
    assert not at.exception

test_total_and_warning()
print("passed")                         # run with pytest in a real project

Remember

  • AppTest runs apps headless: AppTest.from_file('app.py').run()
  • Inspect elements by type or key and interact with set_value, click, input, then .run()
  • Preset session_state and secrets; assert not at.exception

Watch out for

  • Forgetting to call .run() after interacting, so assertions see the previous state.

Check yourself

  1. After `at.button[0].click()`, the assertions still see the old output. What is missing?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.streamlit.io/develop/concepts/app-testing