/
vshmidt
/
streamlit
Обзор
Документация
Войти
/
vshmidt
/
streamlit
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
e2e_playwright/st_selectbox_test.py
469 строк
17 KB
Ken McGrady
Update copyright header to 2026 (#13491)
02 янв 2026, 16:21
Не верифицирован
02 янв 2026, 16:21
374540a
Код
Авторство
О чём код?
# 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. from __future__ import annotations import re from typing import TYPE_CHECKING from playwright.sync_api import Locator, Page, expect 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_selectbox, select_selectbox_option, ) if TYPE_CHECKING: from e2e_playwright.conftest import ImageCompareFunction NUM_SELECTBOXES = 21 def get_selectbox_input( locator: Locator | Page, label: str | re.Pattern[str] ) -> Locator: """Get the input of a selectbox with the given label. Parameters ---------- locator : Locator | Page The locator to search for the element. Returns ------- Locator The input of the element. """ return get_selectbox(locator, label).locator("input").first def test_selectbox_widget_rendering( themed_app: Page, assert_snapshot: ImageCompareFunction ): """Test that the selectbox widgets are correctly rendered via screenshot matching.""" selectbox_widgets = themed_app.get_by_test_id("stSelectbox") expect(selectbox_widgets).to_have_count(NUM_SELECTBOXES) assert_snapshot( get_selectbox(themed_app, "selectbox 1 (default)"), name="st_selectbox-default" ) assert_snapshot( get_selectbox(themed_app, "selectbox 2 (formatted options)"), name="st_selectbox-formatted_options", ) assert_snapshot( get_selectbox(themed_app, "selectbox 3 (no options)"), name="st_selectbox-no_options", ) assert_snapshot( get_selectbox(themed_app, "selectbox 4 (more options)"), name="st_selectbox-more_options", ) assert_snapshot( get_selectbox(themed_app, "selectbox 5 (disabled)"), name="st_selectbox-disabled", ) assert_snapshot( get_selectbox(themed_app, "selectbox 6 (hidden label)"), name="st_selectbox-hidden_label", ) assert_snapshot( get_selectbox(themed_app, "selectbox 7 (collapsed label)"), name="st_selectbox-collapsed_label", ) assert_snapshot( get_selectbox(themed_app, "selectbox 8 (with callback, help)"), name="st_selectbox-callback_help", ) assert_snapshot( get_selectbox(themed_app, "selectbox 9 (empty selection)"), name="st_selectbox-empty_selection", ) assert_snapshot( get_selectbox(themed_app, "selectbox 10 (empty, custom placeholder)"), name="st_selectbox-empty_selection_placeholder", ) assert_snapshot( get_selectbox(themed_app, "selectbox 11 (options from dataframe)"), name="st_selectbox-dataframe_options", ) assert_snapshot( get_selectbox(themed_app, "selectbox 12 (empty, value from state)"), name="st_selectbox-value_from_state", ) assert_snapshot( get_selectbox(themed_app, re.compile(r"^selectbox 13")), name="st_selectbox-markdown_label", ) assert_snapshot( get_selectbox(themed_app, "selectbox 18 (width=200px)"), name="st_selectbox-width_200px", ) assert_snapshot( get_selectbox(themed_app, "selectbox 19 (width='stretch')"), name="st_selectbox-width_stretch", ) def test_selectbox_has_correct_initial_values(app: Page): """Test that st.selectbox returns the correct initial values.""" expect_markdown(app, "value 1: male") expect_markdown(app, "value 2: female") expect_markdown(app, "value 3: None") expect_markdown(app, "value 4: e2e/scripts/components_iframe.py") expect_markdown(app, "value 5: male") expect_markdown(app, "value 6: male") expect_markdown(app, "value 7: male") expect_markdown(app, "value 8: female") expect_markdown(app, "selectbox changed: False") expect_markdown(app, "value 9: None") expect_markdown(app, "value 10: None") expect_markdown(app, "value 11: male") expect_markdown(app, "value 12: female") expect_markdown(app, "value 14: male") expect_markdown(app, "value 15: male") expect_markdown(app, "value 15 (session_state): male") expect_markdown(app, "value 16: female") expect_markdown(app, "value 17: None") def test_handles_option_selection(app: Page, assert_snapshot: ImageCompareFunction): """Test that selection of an option via the dropdown works correctly.""" get_selectbox_input(app, "selectbox 4 (more options)").click() # Take a snapshot of the selection dropdown: selection_dropdown = app.locator('[data-baseweb="popover"]').first assert_snapshot(selection_dropdown, name="st_selectbox-selection_dropdown") # Select last option: selection_dropdown.locator("li").nth(1).click() # Check that selection worked: expect_markdown(app, "value 4: e2e/scripts/st_warning.py") def test_handles_option_selection_via_typing(app: Page): """Test that selection of an option via typing works correctly.""" selectbox_input = get_selectbox_input(app, "selectbox 4 (more options)") # Type an option: selectbox_input.type("e2e/scripts/st_warning.py") selectbox_input.press("Enter") # Check that selection worked: expect_markdown(app, "value 4: e2e/scripts/st_warning.py") def test_shows_correct_options_via_fuzzy_search( app: Page, assert_snapshot: ImageCompareFunction ): """Test that the fuzzy matching of options via typing works correctly.""" selectbox_input = get_selectbox_input(app, "selectbox 4 (more options)") # Start typing: selectbox_input.type("exp") # Check filtered options selection_dropdown = app.locator('[data-baseweb="popover"]').first assert_snapshot(selection_dropdown, name="st_selectbox-fuzzy_matching") def test_empty_selectbox_behaves_correctly( app: Page, assert_snapshot: ImageCompareFunction ): """Test that st.selectbox behaves correctly when empty (no initial selection).""" empty_selectbox_input = get_selectbox_input(app, "selectbox 9 (empty selection)") # Type an option: empty_selectbox_input.type("male") empty_selectbox_input.press("Enter") expect_markdown(app, "value 9: male") assert_snapshot( get_selectbox(app, "selectbox 9 (empty selection)"), name="st_selectbox-clearable_input", ) empty_selectbox_input.focus() empty_selectbox_input.press("Escape") # Should be empty again: expect_markdown(app, "value 9: None") def test_keeps_value_on_selection_close(app: Page): """Test that the selection is kept when the dropdown is closed.""" get_selectbox_input(app, "selectbox 4 (more options)").click() # Take a snapshot of the selection dropdown: expect(app.locator('[data-baseweb="popover"]').first).to_be_visible() # Click outside to close the dropdown: app.get_by_test_id("stMarkdown").first.click() # Check if value is still initial value: expect_markdown(app, "value 4: e2e/scripts/components_iframe.py") def test_handles_callback_on_change_correctly(app: Page): """Test that it correctly calls the callback on change.""" # Check initial state: expect_markdown(app, "value 8: female") expect_markdown(app, "selectbox changed: False") get_selectbox_input(app, "selectbox 8 (with callback, help)").click() # Select last option: selection_dropdown = app.locator('[data-baseweb="popover"]').first selection_dropdown.locator("li").first.click() # Check that selection worked: expect_markdown(app, "value 8: male") expect_markdown(app, "selectbox changed: True") expect( app.get_by_text("Selectbox widget callback triggered: x=1, y=2, z=3") ).to_be_visible() # Change different input to trigger delta path change empty_selectbox_input = get_selectbox_input(app, "selectbox 1 (default)") # Type an option: empty_selectbox_input.type("female") empty_selectbox_input.press("Enter") expect_markdown(app, "value 1: female") expect_markdown(app, "value 8: male") expect_markdown(app, "selectbox changed: False") def test_check_top_level_class(app: Page): """Check that the top level class is correctly set.""" check_top_level_class(app, "stSelectbox") 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, "selectbox8")).to_be_visible() def test_dynamic_selectbox_props(app: Page, assert_snapshot: ImageCompareFunction): """Test that the selectbox can be updated dynamically while keeping the state. This tests that: 1. Options can be changed dynamically when a key is provided 2. Format function can be changed dynamically 3. Selection resets to default when selected value is removed from options 4. Selection is preserved when the selected value exists in new options Initial options: [apple, banana, mango, orange] with format_func=capitalize, index=0 (default: apple) Updated options: [mango, papaya, grape, apple] with format_func=upper, index=1 (default: papaya) """ dynamic_select = get_element_by_key(app, "dynamic_selectbox_with_key") expect(dynamic_select).to_be_visible() # Initial state expect(dynamic_select).to_contain_text("Initial dynamic selectbox") expect_prefixed_markdown(app, "Initial selectbox value:", "apple") assert_snapshot(dynamic_select, name="st_selectbox-dynamic_initial") # Check that the help tooltip is correct: expect_help_tooltip(app, dynamic_select, "initial help") # --- Test 1: Selection RESETS when value is removed from options --- # Select "banana" (only exists in initial options, NOT in updated) select_selectbox_option(app, "Initial dynamic selectbox", "Banana") expect_prefixed_markdown(app, "Initial selectbox value:", "banana") # Toggle to update props - options change from [apple, banana, mango, orange] # to [mango, papaya, grape, apple]. "banana" is NOT in updated options. click_toggle(app, "Update selectbox props") # Updated selectbox is visible expect(dynamic_select).to_contain_text("Updated dynamic selectbox") # Selection should RESET to "papaya" (default at index=1) since "banana" is not in updated options expect_prefixed_markdown(app, "Updated selectbox value:", "papaya") dynamic_select.scroll_into_view_if_needed() assert_snapshot(dynamic_select, name="st_selectbox-dynamic_updated") # Check that the help tooltip is correct: expect_help_tooltip(app, dynamic_select, "updated help") # --- Test 2: Selection PRESERVED when value exists in both option sets --- # Select "mango" - it exists in BOTH option sets at different indices: # Initial: index 2 (displayed "Mango"), Updated: index 0 (displayed "MANGO") # Neither is the default. This ensures we're testing true preservation. select_selectbox_option(app, "Updated dynamic selectbox", "MANGO") expect_prefixed_markdown(app, "Updated selectbox value:", "mango") # Toggle back to initial options - "mango" exists in initial too click_toggle(app, "Update selectbox props") expect(dynamic_select).to_contain_text("Initial dynamic selectbox") # Selection should be PRESERVED since "mango" is in both option sets # If this was reset, it would show "apple" (initial default), not "mango" expect_prefixed_markdown(app, "Initial selectbox value:", "mango") def test_dismiss_change_by_clicking_away(app: Page): """Test that pressing ESC during editing restores the original value.""" # Initial check expect_markdown(app, "value 14: male") # Get selectbox input selectbox_element = get_selectbox(app, "selectbox 14 (test dismiss behavior)") selectbox_input = selectbox_element.locator("input") # Click to focus the input selectbox_input.click() # Clear part of the text and type something else selectbox_input.press("Backspace") selectbox_input.press("Backspace") selectbox_input.press("Backspace") selectbox_input.type("xyz") # Verify the input value is indeed updated expect(selectbox_input).to_have_value("mxyz") # Press click outside of the input field to close the dropdown and stop editing app.get_by_test_id("stMarkdownContainer").get_by_text( "selectbox 14 (test dismiss behavior)" ).click() # Verify original value is restored # We use contain_text because the selectbox_element's text also includes the label expect(selectbox_element).to_contain_text("male") expect_markdown(app, "value 14: male") def test_accept_new_options_feature(app: Page): """Test that the accept_new_options feature works correctly. When it's True, the user must be able to enter a new option that doesn't exist in the original options. """ # Get the selectbox with accept_new_options=True selectbox_input = get_selectbox_input(app, "selectbox 15 (accept new options)") # Type a new option that doesn't exist in the original options selectbox_input.click() selectbox_input.fill("") # Clear the input selectbox_input.type("new_custom_option") selectbox_input.press("Enter") # Check that the new option was accepted and selected expect_markdown(app, "value 15: new_custom_option") expect_markdown(app, "value 15 (session_state): new_custom_option") def test_does_not_accept_new_options_feature(app: Page): """Test that the accept_new_options feature works correctly. When it's False, the user must not be able to enter a new option that doesn't exist in the original options. """ # Get any selectbox with accept_new_options=False selectbox_input = get_selectbox_input(app, "selectbox 1 (default)") expect_markdown(app, "value 1: male") # Type a new option that doesn't exist in the original options selectbox_input.click() selectbox_input.fill("") # Clear the input selectbox_input.type("new_custom_option") selectbox_input.press("Enter") expect_markdown(app, "value 1: male") def test_selectbox_preset_session_state(app: Page): """Should display values from session_state.""" expect_markdown(app, "value 16: female") selectbox = get_selectbox(app, "selectbox 16 - session_state values") expect(selectbox.get_by_text("female", exact=True)).to_be_visible() def test_selectbox_empty_options_with_accept_new_options(app: Page): """Should allow adding new options when options list is empty but accept_new_options is True. """ # Get the selectbox with empty options but accept_new_options=True selectbox_elem = get_selectbox( app, "selectbox 17 - empty options with accept_new_options" ) selectbox_input = selectbox_elem.locator("input") # Verify the initial placeholder shows a message about adding an option expect(selectbox_elem).to_contain_text("Add an option") # Click to focus the input field selectbox_input.click() # Type and add a new option "new_option" selectbox_input.fill("new_option") selectbox_input.press("Enter") # Verify new option was added and selected successfully expect_markdown(app, "value 17: new_option") # Verify the new option is visible in the input field expect(selectbox_elem.get_by_text("new_option", exact=True)).to_be_visible() # Add another option to replace the first one selectbox_input.click() selectbox_input.fill("another_option") selectbox_input.press("Enter") # Verify the new option replaced the previous one expect_markdown(app, "value 17: another_option") expect(selectbox_elem.get_by_text("another_option", exact=True)).to_be_visible() def test_help_tooltip_works(app: Page): element_with_help = get_selectbox(app, "selectbox 8 (with callback, help)") expect_help_tooltip(app, element_with_help, "Help text") def test_selectbox_session_state_sync_after_open_close(app: Page): """Regression test for https://github.com/streamlit/streamlit/issues/13435. When value is set via session_state and user opens/closes dropdown without selecting, the UI should remain in sync with the actual value. """ # Initial state should show "male" (default at index 0) selectbox = get_selectbox(app, "selectbox 20 - session_state sync test") expect(selectbox.get_by_text("male", exact=True)).to_be_visible() expect_markdown(app, "value 20: male") # Click button to set value to "female" via session_state app.get_by_role("button", name="Set female").click() expect_markdown(app, "value 20: female") expect(selectbox.get_by_text("female", exact=True)).to_be_visible() # Open the dropdown selectbox_input = get_selectbox_input(app, "selectbox 20 - session_state sync test") selectbox_input.click() # Verify dropdown is open expect(app.locator('[data-baseweb="popover"]').first).to_be_visible() # Close by pressing Escape without making a selection app.keyboard.press("Escape") # The selectbox should still display "female" (not revert to initial "male") expect(selectbox.get_by_text("female", exact=True)).to_be_visible() expect_markdown(app, "value 20: female")