Skip to content

Commit

Permalink
Add file_uploader playwright tests (streamlit#7498)
Browse files Browse the repository at this point in the history
Playwright tests for st.file_uploader
  • Loading branch information
kajarenc authored Oct 14, 2023
1 parent 71f49ec commit aa33a69
Show file tree
Hide file tree
Showing 41 changed files with 333 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions e2e_playwright/st_file_uploader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
#
# 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 streamlit as st
from streamlit import runtime

single_file = st.file_uploader("Drop a file:", type=["txt"], key="single")
if single_file is None:
st.text("No upload")
else:
st.text(single_file.read())

# Here and throughout this file, we use `if runtime.is_running():`
# since we also run e2e python files in "bare Python mode" as part of our
# Python tests, and this doesn't work in that circumstance
# st.session_state can only be accessed while running with streamlit
if runtime.exists():
st.write(repr(st.session_state.single) == repr(single_file))

disabled = st.file_uploader(
"Can't drop a file:", type=["txt"], key="disabled", disabled=True
)
if disabled is None:
st.text("No upload")
else:
st.text(disabled.read())

if runtime.exists():
st.write(repr(st.session_state.disabled) == repr(disabled))

multiple_files = st.file_uploader(
"Drop multiple files:",
type=["txt"],
accept_multiple_files=True,
key="multiple",
)
if multiple_files is None:
st.text("No upload")
else:
files = [file.read().decode() for file in multiple_files]
st.text("\n".join(files))

if runtime.exists():
st.write(repr(st.session_state.multiple) == repr(multiple_files))

with st.form("foo"):
form_file = st.file_uploader("Inside form:", type=["txt"])
st.form_submit_button("Submit")
if form_file is None:
st.text("No upload")
else:
st.text(form_file.read())


hidden_label = st.file_uploader(
"Hidden label:",
key="hidden_label",
label_visibility="hidden",
)

if hidden_label is None:
st.text("No upload")
else:
st.text(hidden_label.read())

if runtime.exists():
st.write(repr(st.session_state.hidden_label) == repr(hidden_label))

collapsed_label = st.file_uploader(
"Collapsed label:",
key="collapsed_label",
label_visibility="collapsed",
)

if collapsed_label is None:
st.text("No upload")
else:
st.text(collapsed_label.read())

if runtime.exists():
st.write(repr(st.session_state.collapsed_label) == repr(collapsed_label))

if runtime.exists():
if not st.session_state.get("counter"):
st.session_state["counter"] = 0

def file_uploader_on_change():
st.session_state.counter += 1

st.file_uploader(
"Drop a file:",
type=["txt"],
key="on_change_file_uploader_key",
on_change=file_uploader_on_change,
)

st.text(st.session_state.counter)
225 changes: 225 additions & 0 deletions e2e_playwright/st_file_uploader_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
#
# 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 playwright.sync_api import Page, expect

from e2e_playwright.conftest import ImageCompareFunction, wait_for_app_run


def test_file_uploader_render_correctly(
themed_app: Page, assert_snapshot: ImageCompareFunction
):
"""Test that the file uploader render as expected via screenshot matching."""
file_uploaders = themed_app.get_by_test_id("stFileUploader")
expect(file_uploaders).to_have_count(7)

assert_snapshot(file_uploaders.nth(0), name="st_single-file-uploader")
assert_snapshot(file_uploaders.nth(1), name="st_disabled-file-uploader")
assert_snapshot(file_uploaders.nth(2), name="st_multi-file-uploader")
assert_snapshot(file_uploaders.nth(4), name="st_hidden-label-file-uploader")
assert_snapshot(file_uploaders.nth(5), name="st_collapsed-label-file-uploader")


def test_file_uploader_error_message_disallowed_files(
app: Page, assert_snapshot: ImageCompareFunction
):
"""Test that shows error message for disallowed files."""
file_name1 = "example.json"
file_content1 = b"{}"

uploader_index = 0

with app.expect_file_chooser() as fc_info:
app.get_by_test_id("stFileUploadDropzone").nth(uploader_index).click()

file_chooser = fc_info.value
file_chooser.set_files(
files=[
{
"name": file_name1,
"mimeType": "application/json",
"buffer": file_content1,
}
]
)

wait_for_app_run(app)
app.wait_for_timeout(1000)

expect(
app.get_by_test_id("stUploadedFileErrorMessage").nth(uploader_index)
).to_have_text("application/json files are not allowed.", use_inner_text=True)

file_uploader_in_error_state = app.get_by_test_id("stFileUploader").nth(
uploader_index
)

assert_snapshot(file_uploader_in_error_state, name="st_file_uploader-error")


def test_uploads_and_deletes_single_file_only(
app: Page, assert_snapshot: ImageCompareFunction
):
"""Test that uploading a file for single file uploader works as expected."""
file_name1 = "file1.txt"
file_content1 = b"file1content"

file_name2 = "file2.txt"
file_content2 = b"file2content"

uploader_index = 0

with app.expect_file_chooser() as fc_info:
app.get_by_test_id("stFileUploadDropzone").nth(uploader_index).click()

file_chooser = fc_info.value
file_chooser.set_files(
files=[{"name": file_name1, "mimeType": "text/plain", "buffer": file_content1}]
)
wait_for_app_run(app)
app.wait_for_timeout(1000)

expect(app.locator(".uploadedFileName")).to_have_text(
file_name1, use_inner_text=True
)

expect(app.get_by_test_id("stText").nth(uploader_index)).to_have_text(
str(file_content1), use_inner_text=True
)

file_uploader_uploaded_state = app.get_by_test_id("stFileUploader").nth(
uploader_index
)

assert_snapshot(
file_uploader_uploaded_state, name="st_single_file_uploader-uploaded"
)

expect(
app.get_by_test_id("stMarkdownContainer").nth(uploader_index + 1)
).to_have_text("True", use_inner_text=True)

# Upload a second file. This one will replace the first.
with app.expect_file_chooser() as fc_info:
app.get_by_test_id("stFileUploadDropzone").nth(uploader_index).click()

file_chooser = fc_info.value
file_chooser.set_files(
files=[{"name": file_name2, "mimeType": "text/plain", "buffer": file_content2}]
)

wait_for_app_run(app)
app.wait_for_timeout(1000)

expect(app.locator(".uploadedFileName")).to_have_text(
file_name2, use_inner_text=True
)

expect(app.get_by_test_id("stText").nth(uploader_index)).to_have_text(
str(file_content2), use_inner_text=True
)

expect(
app.get_by_test_id("stMarkdownContainer").nth(uploader_index + 1)
).to_have_text("True", use_inner_text=True)

app.get_by_test_id("stHeader").press("r")

wait_for_app_run(app)
app.wait_for_timeout(1000)

expect(app.get_by_test_id("stText").nth(uploader_index)).to_have_text(
str(file_content2), use_inner_text=True
)

app.get_by_test_id("fileDeleteBtn").nth(uploader_index).click()

wait_for_app_run(app)
app.wait_for_timeout(1000)

expect(app.get_by_test_id("stText").nth(uploader_index)).to_have_text(
"No upload", use_inner_text=True
)


def test_uploads_and_deletes_multiple_files(
app: Page, assert_snapshot: ImageCompareFunction
):
"""Test that uploading multiple files at once works correctly."""
file_name1 = "file1.txt"
file_content1 = b"file1content"

file_name2 = "file2.txt"
file_content2 = b"file2content"

files = [
{"name": file_name1, "mimeType": "text/plain", "buffer": file_content1},
{"name": file_name2, "mimeType": "text/plain", "buffer": file_content2},
]

uploader_index = 2

with app.expect_file_chooser() as fc_info:
app.get_by_test_id("stFileUploadDropzone").nth(uploader_index).click()

file_chooser = fc_info.value
file_chooser.set_files(files=files)

wait_for_app_run(app)
app.wait_for_timeout(1000)

uploaded_file_names = app.locator(".uploadedFileName")

# The widget should show the names of the uploaded files in reverse order
file_names = [files[1]["name"], files[0]["name"]]

for i, element in enumerate(uploaded_file_names.all()):
expect(element).to_have_text(file_names[i], use_inner_text=True)

# The script should have printed the contents of the two files into a st.text.
# This tests that the upload actually went through.
content = "\n".join(
[
files[0]["buffer"].decode("utf-8"),
files[1]["buffer"].decode("utf-8"),
]
)
expect(app.get_by_test_id("stText").nth(uploader_index)).to_have_text(
content, use_inner_text=True
)

file_uploader = app.get_by_test_id("stFileUploader").nth(uploader_index)
assert_snapshot(file_uploader, name="st_multi_file_uploader-uploaded")

# Delete the second file. The second file is on top because it was
# most recently uploaded. The first file should still exist.
app.get_by_test_id("fileDeleteBtn").first.click()

wait_for_app_run(app)
app.wait_for_timeout(1000)

expect(app.get_by_test_id("stText").nth(uploader_index)).to_have_text(
files[0]["buffer"].decode("utf-8"), use_inner_text=True
)

expect(app.get_by_test_id("stMarkdownContainer").nth(5)).to_have_text(
"True", use_inner_text=True
)


# TODO(kajarenc): Migrate missing test from cypress test spec st_file_uploader.spec.js
# to playwright:
# - uploads and deletes multiple files quickly / slowly
# - works inside st.form()
# - does not call a callback when not changed

0 comments on commit aa33a69

Please sign in to comment.