Skip to content

Commit

Permalink
Run bare execution tests based on playwright scripts (streamlit#8616)
Browse files Browse the repository at this point in the history
## Describe your changes

Change the target directory of the run bare integration test to
`e2e_playwright` instead of the cypress e2e tests. This also required
some smaller refactorings to better support bare executions.

This also moves the bare execution to its own dedicated Github actions
workflow.

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this
project are made under the Apache 2.0 license.
  • Loading branch information
lukasmasuch authored May 7, 2024
1 parent ee5509d commit cf940ea
Show file tree
Hide file tree
Showing 14 changed files with 146 additions and 105 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/python-bare-executions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Python Bare Executions

on:
push:
branches:
- "develop"
pull_request:
types: [opened, synchronize, reopened]
# Allows workflow to be called from other workflows
workflow_call:
inputs:
ref:
required: true
type: string

# Avoid duplicate workflows on same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-bare-executions
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest

defaults:
run:
shell: bash --login -eo pipefail {0}

steps:
- name: Checkout Streamlit code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
persist-credentials: false
submodules: "recursive"
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup virtual env
uses: ./.github/actions/make_init
- name: Run make develop
run: make develop
- name: Run Bare Execution Tests
run: make bare-execution-tests
2 changes: 0 additions & 2 deletions .github/workflows/python-min-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ jobs:
run: uv pip install --editable lib --no-deps
- name: Run Python Tests
run: make pytest
- name: Run Integration Tests
run: make integration-tests
- name: CLI Smoke Tests
run: make cli-smoke-tests
- name: Validate min-constraints-gen
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/python-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ jobs:
run: scripts/mypy --report
- name: Run Python Tests
run: make pytest
- name: Run Integration Tests
run: make integration-tests
- name: CLI Smoke Tests
run: make cli-smoke-tests
- name: Set CONSTRAINTS_FILE env variable
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ pytest-snowflake:
mypy:
./scripts/mypy

.PHONY: integration-tests
.PHONY: bare-execution-tests
# Run all our e2e tests in "bare" mode and check for non-zero exit codes.
integration-tests:
python3 scripts/run_bare_integration_tests.py
bare-execution-tests:
python3 scripts/run_bare_execution_tests.py

.PHONY: cli-smoke-tests
# Verify that CLI boots as expected when called with `python -m streamlit`
Expand Down
15 changes: 15 additions & 0 deletions e2e_playwright/st_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,18 @@ def dialog_in_sidebar():

if st.button("Open Sidebar-Dialog"):
dialog_in_sidebar()


@st.experimental_dialog("Level2 Dialog")
def level2_dialog():
st.write("Second level dialog")


@st.experimental_dialog("Level1 Dialog")
def level1_dialog():
st.write("First level dialog")
level2_dialog()


if st.button("Open Nested Dialogs"):
level1_dialog()
29 changes: 0 additions & 29 deletions e2e_playwright/st_dialog_nested.py

This file was deleted.

24 changes: 0 additions & 24 deletions e2e_playwright/st_dialog_nested_test.py

This file was deleted.

11 changes: 11 additions & 0 deletions e2e_playwright/st_dialog_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,14 @@ def test_sidebardialog_displays_correctly(
dialog = app.get_by_role("dialog")
expect(dialog.get_by_test_id("stButton")).to_be_visible()
assert_snapshot(dialog, name="st_dialog-in_sidebar")


def test_nested_dialogs(app: Page):
"""Test that st.dialog may not be nested inside other dialogs."""
app.get_by_text("Open Nested Dialogs").click()
wait_for_app_run(app)
exception_message = app.get_by_test_id("stException")

expect(exception_message).to_contain_text(
"StreamlitAPIException: Dialogs may not be nested inside other dialogs."
)
4 changes: 2 additions & 2 deletions e2e_playwright/st_expander.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ def update_value():

update_button = st.button("Update Num Input", on_click=update_value)

st.text(st.session_state.number)
st.text(st.session_state.get("number"))

if st.button("Print State Value"):
st.text(st.session_state.number)
st.text(st.session_state.get("number"))

expander_long = st.expander(
"Expand me! "
Expand Down
10 changes: 8 additions & 2 deletions e2e_playwright/st_plotly_chart_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@
)
st.header("Bubble Chart with Box Select")
st.plotly_chart(fig_bubble, on_select="rerun", key="bubble_chart", selection_mode="box")
if len(st.session_state.bubble_chart.select["points"]) > 0:
if (
st.session_state.get("bubble_chart")
and len(st.session_state.bubble_chart.select["points"]) > 0
):
st.write("The original df data selected:")
points = st.session_state.bubble_chart.select["points"]
# Extract x and y values directly into lists
Expand All @@ -55,7 +58,10 @@
st.plotly_chart(
fig_linechart, on_select="rerun", key="line_chart", selection_mode=["lasso"]
)
if len(st.session_state.line_chart.select["points"]) > 0:
if (
st.session_state.get("line_chart")
and len(st.session_state.line_chart.select["points"]) > 0
):
st.write("The original df data selected:")
points = st.session_state.line_chart.select["points"]
# Extract x and y values directly into lists
Expand Down
12 changes: 12 additions & 0 deletions e2e_playwright/st_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
# limitations under the License.

import streamlit as st
from streamlit.runtime.scriptrunner import get_script_run_ctx

ctx = get_script_run_ctx()
if ctx is None:
import sys

# This script is not compatible with running it in "bare" mode (e.g. `python script.py`)
# The reason is that the mutable container is not correctly returned if
# the runtime doesn't exist.
print("This test script does not support bare script execution.")
sys.exit(0)


running_status = st.status("Running status", expanded=True)
running_status.write("Doing some work...")
Expand Down
36 changes: 23 additions & 13 deletions e2e_playwright/st_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,21 @@
# limitations under the License.

import time
from os.path import abspath, dirname, join

import requests

import streamlit as st

# Construct test assets path relative to this script file to
# allow its execution with different working directories.
TEST_ASSETS_DIR = join(dirname(abspath(__file__)), "test_assets")

WEBM_VIDEO_PATH = join(TEST_ASSETS_DIR, "sintel-short.webm")
MP4_VIDEO_PATH = join(TEST_ASSETS_DIR, "sintel-short.mp4")
VTT_EN_PATH = join(TEST_ASSETS_DIR, "sintel-en.vtt")
VTT_DE_PATH = join(TEST_ASSETS_DIR, "sintel-de.vtt")

url = "https://www.w3schools.com/html/mov_bbb.mp4"
file = requests.get(url).content
st.video(file)
Expand All @@ -27,48 +37,48 @@
st.video(url, start_time=int(timestamp))

# Test local file with video
st.video("test_assets/sintel-short.mp4", start_time=17)
st.video(MP4_VIDEO_PATH, start_time=17)

# Test subtitle with video
st.video(
"test_assets/sintel-short.mp4",
MP4_VIDEO_PATH,
start_time=31,
subtitles={
"English": "test_assets/sintel-en.vtt",
"Deutsch": "test_assets/sintel-de.vtt",
"English": VTT_EN_PATH,
"Deutsch": VTT_DE_PATH,
},
)

# Test subtitle with webm video
st.video(
"test_assets/sintel-short.webm",
WEBM_VIDEO_PATH,
start_time=25,
subtitles={
"English": "test_assets/sintel-en.vtt",
"Deutsch": "test_assets/sintel-de.vtt",
"English": VTT_EN_PATH,
"Deutsch": VTT_DE_PATH,
},
)


# Test end time webm video
st.video(
"test_assets/sintel-short.webm",
WEBM_VIDEO_PATH,
start_time=31,
end_time=33,
)

# Test end time mp4 video
st.video(
"test_assets/sintel-short.mp4",
MP4_VIDEO_PATH,
start_time=31,
end_time=33,
)

# Test end time and loop webm video
st.video("test_assets/sintel-short.webm", start_time=35, end_time=39, loop=True)
st.video(WEBM_VIDEO_PATH, start_time=35, end_time=39, loop=True)

# Test end time and loop mp4 video
st.video("test_assets/sintel-short.mp4", start_time=35, end_time=39, loop=True)
st.video(MP4_VIDEO_PATH, start_time=35, end_time=39, loop=True)

# Test autoplay with video
autoplay = st.checkbox("Autoplay", value=False)
Expand All @@ -81,13 +91,13 @@
st.write("Another element")

st.video(
"test_assets/sintel-short.webm",
WEBM_VIDEO_PATH,
autoplay=autoplay,
)

# Test muted with video
st.video(
"test_assets/sintel-short.webm",
WEBM_VIDEO_PATH,
autoplay=True,
muted=True,
)
14 changes: 11 additions & 3 deletions lib/streamlit/runtime/state/session_state_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@


_state_use_warning_already_displayed: bool = False
# The mock session state is used as a fallback if the script is run without `streamlit run`
_mock_session_state: SafeSessionState | None = None


def get_session_state() -> SafeSessionState:
Expand All @@ -42,16 +44,22 @@ def get_session_state() -> SafeSessionState:

ctx = get_script_run_ctx()

# If there is no script run context because the script is run bare, have
# session state act as an always empty dictionary, and print a warning.
# If there is no script run context because the script is run bare, we
# use a global mock session state version to allow bare script execution (via python script.py)
if ctx is None:
if not _state_use_warning_already_displayed:
_state_use_warning_already_displayed = True
if not runtime.exists():
_LOGGER.warning(
"Session state does not function when running a script without `streamlit run`"
)
return SafeSessionState(SessionState(), lambda: None)

global _mock_session_state

if _mock_session_state is None:
# Lazy initialize the mock session state
_mock_session_state = SafeSessionState(SessionState(), lambda: None)
return _mock_session_state
return ctx.session_state


Expand Down
Loading

0 comments on commit cf940ea

Please sign in to comment.