/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
e2e_playwright/st_text_input_test.py
446 строк
16 KB
Maya Barnes
Bind widgets to query params - `st.text_input` & `st.text_area` (#13901)
12 фев 2026, 02:33
Не верифицирован
12 фев 2026, 02:33
f345cbe
Код
Авторство
О чём код?
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from playwright.sync_api import Page, expect from e2e_playwright.conftest import ( ImageCompareFunction, wait_for_app_loaded, wait_for_app_run, ) from e2e_playwright.shared.app_utils import ( check_top_level_class, click_toggle, expect_help_tooltip, expect_markdown, expect_prefixed_markdown, get_element_by_key, get_text_input, ) from e2e_playwright.shared.input_utils import ( expect_global_hotkeys_not_fired, type_common_characters_into_input, ) TEXT_INPUT_ELEMENTS = 22 def test_text_input_widget_rendering( themed_app: Page, assert_snapshot: ImageCompareFunction ): """Test that the st.text_input widgets are correctly rendered via screenshot matching.""" text_input_widgets = themed_app.get_by_test_id("stTextInput") expect(text_input_widgets).to_have_count(TEXT_INPUT_ELEMENTS) assert_snapshot( get_text_input(themed_app, "text input 1 (default)"), name="st_text_input-default", ) assert_snapshot( get_text_input(themed_app, "text input 2 (value='some text')"), name="st_text_input-value_some_text", ) assert_snapshot( get_text_input(themed_app, "text input 3 (value=1234)"), name="st_text_input-value_1234", ) assert_snapshot( get_text_input(themed_app, "text input 4 (value=None)"), name="st_text_input-value_None", ) assert_snapshot( get_text_input(themed_app, "text input 5 (placeholder)"), name="st_text_input-placeholder", ) assert_snapshot( get_text_input(themed_app, "text input 6 (disabled)"), name="st_text_input-disabled", ) assert_snapshot( get_text_input(themed_app, "text input 7 (hidden label)"), name="st_text_input-hidden_label", ) assert_snapshot( get_text_input(themed_app, "text input 8 (collapsed label)"), name="st_text_input-collapsed_label", ) assert_snapshot( get_element_by_key(themed_app, "text_input_9"), name="st_text_input-callback_help", ) assert_snapshot( get_text_input(themed_app, "text input 10 (max_chars=5)"), name="st_text_input-max_chars_5", ) assert_snapshot( get_text_input(themed_app, "text input 11 (type=password)"), name="st_text_input-type_password", ) assert_snapshot( get_element_by_key(themed_app, "text_input_14"), name="st_text_input-markdown_label", ) assert_snapshot( get_text_input(themed_app, "text input 15 - emoji icon"), name="st_text_input-emoji_icon", ) assert_snapshot( get_text_input(themed_app, "text input 16 - material icon"), name="st_text_input-material_icon", ) assert_snapshot( get_text_input(themed_app, "text input 17 (width=200px)"), name="st_text_input-width_200px", ) assert_snapshot( get_text_input(themed_app, "text input 18 (width='stretch')"), name="st_text_input-width_stretch", ) def test_text_input_has_correct_initial_values(app: Page): """Test that st.text_input has the correct initial values.""" expect_markdown(app, "value 1: ") expect_markdown(app, "value 2: some text") expect_markdown(app, "value 3: 1234") expect_markdown(app, "value 4: None") expect_markdown(app, "value 5: ") expect_markdown(app, "value 6: default text") expect_markdown(app, "value 7: default text") expect_markdown(app, "value 8: default text") expect_markdown(app, "value 9: ") expect_markdown(app, "text input changed: False") expect_markdown(app, "value 10: 1234") expect_markdown(app, "value 11: my password") expect_markdown(app, "text input 12 (value from state) - value: xyz") expect_markdown(app, "text input 13 (value from form) - value:") expect_markdown(app, "Rerun counter: 1") def test_text_input_typing_common_characters_does_not_trigger_global_hotkeys( app: Page, ) -> None: """Typing into st.text_input must not trigger global hotkeys (e.g. c/r).""" text_input_field = ( get_text_input(app, "text input 1 (default)").locator("input").first ) rerun_counter = app.get_by_text("Rerun counter: 1", exact=True) expect_global_hotkeys_not_fired(app, expected_runs=1, runs_locator=rerun_counter) typed = type_common_characters_into_input( text_input_field, after_each=lambda _ch: expect_global_hotkeys_not_fired( app, expected_runs=1, runs_locator=rerun_counter, ), ) expect(text_input_field).to_have_value(typed) def test_text_input_shows_instructions_when_dirty( app: Page, assert_snapshot: ImageCompareFunction ): """Test that st.text_input shows the instructions correctly when dirty.""" text_input = get_text_input(app, "text input 10 (max_chars=5)") text_input_field = text_input.locator("input").first expect(text_input_field).to_be_visible() text_input_field.fill("123") expect(text_input.get_by_test_id("InputInstructions")).to_have_text( "Press Enter to apply3/5" ) assert_snapshot(text_input, name="st_text_input-input_instructions") def test_text_input_limits_input_via_max_chars(app: Page): """Test that st.text_input correctly limits the number of characters via max_chars.""" text_input_field = ( get_text_input(app, "text input 10 (max_chars=5)").locator("input").first ) expect(text_input_field).to_be_visible() text_input_field.clear() expect(text_input_field).to_have_value("") text_input_field.type("12345678") text_input_field.press("Enter") expect_markdown(app, "value 10: 12345") # Try filling in everything at once: text_input_field.focus() text_input_field.fill("12345678") text_input_field.press("Enter") expect_markdown(app, "value 10: 12345") def test_text_input_has_correct_value_on_blur(app: Page): """Test that st.text_input has the correct value on blur.""" first_text_input_field = ( get_text_input(app, "text input 1 (default)").locator("input").first ) first_text_input_field.focus() first_text_input_field.fill("hello world") first_text_input_field.blur() expect_markdown(app, "value 1: hello world") def test_text_input_has_correct_value_on_enter(app: Page): """Test that st.text_input has the correct value on enter.""" first_text_input_field = ( get_text_input(app, "text input 1 (default)").locator("input").first ) first_text_input_field.focus() first_text_input_field.fill("hello world") first_text_input_field.press("Enter") expect_markdown(app, "value 1: hello world") def test_text_input_has_correct_value_on_click_outside(app: Page): """Test that st.text_input has the correct value on click outside.""" first_text_input_field = ( get_text_input(app, "text input 1 (default)").locator("input").first ) first_text_input_field.focus() first_text_input_field.fill("hello world") app.get_by_test_id("stMarkdown").first.click() expect_markdown(app, "value 1: hello world") def test_text_input_does_not_trigger_rerun_when_value_does_not_change_and_click_outside( app: Page, ): """Test that st.text_input has the correct value on click outside.""" expect_markdown(app, "Rerun counter: 1") first_text_input_field = ( get_text_input(app, "text input 1 (default)").locator("input").first ) first_text_input_field.focus() first_text_input_field.fill("hello world") app.get_by_test_id("stMarkdown").first.click() expect_markdown(app, "value 1: hello world") expect_markdown(app, "Rerun counter: 2") first_text_input_field.focus() app.get_by_test_id("stMarkdown").first.click() expect_markdown(app, "Rerun counter: 2") def test_empty_text_input_behaves_correctly(app: Page): """Test that st.text_input behaves correctly when empty.""" # Should return None as value: expect_markdown(app, "value 4: None") # Enter value in the empty widget: empty_text_input = get_text_input(app, "text input 4 (value=None)") empty_text_input_field = empty_text_input.locator("input").first empty_text_input_field.fill("hello world") empty_text_input_field.press("Enter") expect_markdown(app, "value 4: hello world") # Press escape to clear value: empty_text_input_field.focus() empty_text_input_field.clear() empty_text_input_field.press("Enter") # Should be set to empty string (we don't clear to None for text input): expect_markdown(app, "value 4: ") def test_text_input_shows_state_value(app: Page): expect(get_element_by_key(app, "text_input_12").locator("input")).to_have_value( "xyz" ) def test_calls_callback_on_change(app: Page): """Test that it correctly calls the callback on change.""" text_input_field = get_element_by_key(app, "text_input_9").locator("input").first expect(text_input_field).to_be_visible() text_input_field.fill("hello world") text_input_field.press("Enter") wait_for_app_run(app) expect_prefixed_markdown(app, "value 9:", "hello world") expect_prefixed_markdown(app, "text input changed:", "True") # Change different widget to trigger delta path change first_text_input_field = ( get_text_input(app, "text input 1 (default)").locator("input").first ) expect(first_text_input_field).to_be_visible() first_text_input_field.fill("hello world") first_text_input_field.press("Enter") wait_for_app_run(app) expect_prefixed_markdown(app, "value 1:", "hello world") # Test if value is still correct after delta path change expect_prefixed_markdown(app, "value 9:", "hello world") expect_prefixed_markdown(app, "text input changed:", "False") def test_text_input_in_form_with_submit_by_enter(app: Page): """Test that text area in form can be submitted by pressing Command+Enter.""" text_area_field = ( get_text_input(app, "text input 13 (value from form)").locator("input").first ) text_area_field.fill("hello world") text_area_field.press("Enter") expect_markdown(app, "text input 13 (value from form) - value: hello world") def test_help_tooltip_works(app: Page): """Test that the help tooltip is displayed on hover.""" element_with_help = get_element_by_key(app, "text_input_9") expect_help_tooltip(app, element_with_help, "Help text") def test_check_top_level_class(app: Page): """Check that the top level class is correctly set.""" check_top_level_class(app, "stTextInput") def test_custom_css_class_via_key(app: Page): """Test that the element can have a custom css class via the key argument.""" expect(get_element_by_key(app, "text_input_9")).to_be_visible() def test_dynamic_text_input_props(app: Page, assert_snapshot: ImageCompareFunction): """Test that the text input can be updated dynamically while keeping the state.""" dynamic_text_input = get_element_by_key(app, "dynamic_text_input_with_key") expect(dynamic_text_input).to_be_visible() expect(dynamic_text_input).to_contain_text("Initial dynamic text input") expect_prefixed_markdown(app, "Initial text input value:", "initial") assert_snapshot(dynamic_text_input, name="st_text_input-dynamic_initial") # Check that the help tooltip is correct: expect_help_tooltip(app, dynamic_text_input, "initial help") # Type something and submit input_field = dynamic_text_input.locator("input").first input_field.fill("foo") input_field.press("Enter") wait_for_app_run(app) expect_prefixed_markdown(app, "Initial text input value:", "foo") # Click the toggle to update the text input props click_toggle(app, "Update text input props") # new text input is visible: expect(dynamic_text_input).to_contain_text("Updated dynamic text input") # Ensure the previously entered value remains visible expect_prefixed_markdown(app, "Updated text input value:", "foo") dynamic_text_input.scroll_into_view_if_needed() assert_snapshot(dynamic_text_input, name="st_text_input-dynamic_updated") # Check that the help tooltip is correct: expect_help_tooltip(app, dynamic_text_input, "updated help") # Type something different and submit input_field.fill("bar") input_field.press("Enter") wait_for_app_run(app) expect_prefixed_markdown(app, "Updated text input value:", "bar") def test_text_input_query_param_seeding(page: Page, app_port: int): """Test that text input value can be seeded from URL query params.""" page.goto(f"http://localhost:{app_port}/?bound_text=seeded_value") wait_for_app_loaded(page) expect_prefixed_markdown(page, "bound text value:", "seeded_value") def test_text_input_query_param_updates_url(app: Page): """Test that changing a bound text input updates the URL.""" # Initially empty, no query param in URL expect_prefixed_markdown(app, "bound text value:", "") expect(app).not_to_have_url(re.compile(r"[?&]bound_text=")) # Type a value and press Enter text_input = get_text_input(app, "Bound no default") text_input.locator("input").fill("test_value") text_input.locator("input").press("Enter") wait_for_app_run(app) # URL should now contain the query param expect(app).to_have_url(re.compile(r"bound_text=test_value")) expect_prefixed_markdown(app, "bound text value:", "test_value") # Clear the value text_input.locator("input").fill("") text_input.locator("input").press("Enter") wait_for_app_run(app) # Query param should be removed since value is back to default (empty) expect(app).not_to_have_url(re.compile(r"[?&]bound_text=")) def test_text_input_query_param_default_override(page: Page, app_port: int): """Test text input with default value: seeding and param removal.""" # Load app with query param overriding the "hello" default page.goto(f"http://localhost:{app_port}/?bound_text_default=world") wait_for_app_loaded(page) expect_prefixed_markdown(page, "bound text default value:", "world") # Change back to default ("hello") text_input = get_text_input(page, "Bound with default") text_input.locator("input").fill("hello") text_input.locator("input").press("Enter") wait_for_app_run(page) # Query param should be removed since value is back to default expect(page).not_to_have_url(re.compile(r"[?&]bound_text_default=")) expect_prefixed_markdown(page, "bound text default value:", "hello") def test_text_input_query_param_special_chars(page: Page, app_port: int): """Test that URL-encoded special characters work in query params.""" page.goto(f"http://localhost:{app_port}/?bound_text=hello%20world%21") wait_for_app_loaded(page) expect_prefixed_markdown(page, "bound text value:", "hello world!") def test_text_input_query_param_max_chars_truncation(page: Page, app_port: int): """Test that URL-seeded values exceeding max_chars are truncated.""" page.goto(f"http://localhost:{app_port}/?bound_max=verylongtext") wait_for_app_loaded(page) # Should be truncated to 5 characters expect_prefixed_markdown(page, "bound text max value:", "veryl")