From 5d4b87bf33024ceca0df93b326127d8f5f915836 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Mon, 29 Jan 2024 16:37:50 -0800 Subject: [PATCH 01/24] add test for input task button --- shiny/api-examples/extended_task/app-core.py | 14 +++++ tests/playwright/controls.py | 37 +++++++++++ .../shiny/inputs/input_task_button/app.py | 62 +++++++++++++++++++ .../test_input_task_button.py | 41 ++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 tests/playwright/shiny/inputs/input_task_button/app.py create mode 100644 tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py diff --git a/shiny/api-examples/extended_task/app-core.py b/shiny/api-examples/extended_task/app-core.py index 6fa9cbe8b..b141a9b3d 100644 --- a/shiny/api-examples/extended_task/app-core.py +++ b/shiny/api-examples/extended_task/app-core.py @@ -24,11 +24,18 @@ async def slow_compute(a: int, b: int) -> int: return a + b +# @ui.bind_task_button(button_id="btn2") +async def slow_input_compute(a: int, b: int) -> int: + await asyncio.sleep(3) + return a + b + + with ui.layout_sidebar(): with ui.sidebar(): ui.input_numeric("x", "x", 1) ui.input_numeric("y", "y", 2) ui.input_task_button("btn", "Compute, slowly") + ui.input_task_button("btn2", "Compute 2 slowly") ui.input_action_button("btn_cancel", "Cancel") @reactive.Effect @@ -37,6 +44,13 @@ def handle_click(): # slow_compute.cancel() slow_compute(input.x(), input.y()) + @reactive.Effect + @reactive.event(input.btn2, ignore_none=False) + async def handle_click2(): + # slow_compute.cancel() + val = await slow_input_compute(input.x(), input.y()) + print(val) + @reactive.Effect @reactive.event(input.btn_cancel) def handle_cancel(): diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index 92f8906c1..f8a209ce8 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -790,6 +790,39 @@ def __init__( ) +class InputTaskButton( + _WidthLocM, + _InputActionBase, +): + # id: str, + # label: TagChild, + # *args: TagChild, + # icon: TagChild = None, + # label_busy: TagChild = "Processing...", + # icon_busy: TagChild | MISSING_TYPE = MISSING, + # width: Optional[str] = None, + # type: Optional[str] = "primary", + # auto_reset: bool = True, + # **kwargs: TagAttrValue, + def __init__( + self, + page: Page, + id: str, + ) -> None: + super().__init__( + page, + id=id, + loc=f"button#{id}.bslib-task-button.shiny-bound-input", + ) + + def expect_state(self, state: str, *, timeout: Timeout = None): + expect_attr(self.loc.locator("> bslib-switch-inline"), name="case", value=state, timeout=timeout) + + def expect_label_text(self, value: list[str], *, timeout: Timeout = None): + playwright_expect(self.loc.locator("> bslib-switch-inline > span")).to_have_text(value, timeout=timeout) + + + class InputActionLink(_InputActionBase): # label: TagChild, # icon: TagChild = None, @@ -2169,6 +2202,10 @@ def __init__( ) -> None: super().__init__(page, id=id, loc=f"#{id}.shiny-text-output") + def get_value(self, *, timeout: Timeout = None) -> str: + return self.loc.inner_text(timeout=timeout) + + # TODO-Karan: Add OutputCode class class OutputTextVerbatim(_OutputTextValue): diff --git a/tests/playwright/shiny/inputs/input_task_button/app.py b/tests/playwright/shiny/inputs/input_task_button/app.py new file mode 100644 index 000000000..0b0c86d0e --- /dev/null +++ b/tests/playwright/shiny/inputs/input_task_button/app.py @@ -0,0 +1,62 @@ +import asyncio +from datetime import datetime + +from shiny import reactive, render +from shiny.express import input, ui + +ui.h5("Current time") + + +@render.text() +def current_time(): + reactive.invalidate_later(1) + return datetime.now().strftime("%H:%M:%S") + + +with ui.p(): + "Notice that the time above updates every second, even if you click the button below." + + +@ui.bind_task_button(button_id="btn") +@reactive.extended_task +async def slow_compute(a: int, b: int) -> int: + await asyncio.sleep(3) + return a + b + + +# @ui.bind_task_button(button_id="btn2") +async def slow_input_compute(a: int, b: int) -> int: + await asyncio.sleep(3) + return a + b + + +with ui.layout_sidebar(): + with ui.sidebar(): + ui.input_numeric("x", "x", 1) + ui.input_numeric("y", "y", 2) + ui.input_task_button("btn", "Compute, slowly") + ui.input_task_button("btn2", "Compute 2 slowly", label_busy="Blocking...") + ui.input_action_button("btn_cancel", "Cancel") + + @reactive.Effect + @reactive.event(input.btn, ignore_none=False) + def handle_click(): + # slow_compute.cancel() + slow_compute(input.x(), input.y()) + + @reactive.Effect + @reactive.event(input.btn2, ignore_none=False) + async def handle_click2(): + # slow_compute.cancel() + val = await slow_input_compute(input.x(), input.y()) + + @reactive.Effect + @reactive.event(input.btn_cancel) + def handle_cancel(): + slow_compute.cancel() + + ui.h5("Sum of x and y") + + @render.text + def show_result(): + return str(slow_compute.result()) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py new file mode 100644 index 000000000..7a61eb5db --- /dev/null +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -0,0 +1,41 @@ +import re +import time +from datetime import datetime + +from conftest import ShinyAppProc +from controls import InputTaskButton, OutputText +from playwright.sync_api import Page + + +def click_button_and_assert_time_difference( + button: InputTaskButton, + current_time: OutputText, + button_label: list[str], +) -> int: + button.expect_state("ready") + button.expect_label_text(button_label) + time1 = current_time.get_value() + button.click() + time.sleep(1.5) + button.expect_state("busy") + time2 = current_time.get_value() + original_time = datetime.strptime(time1, "%H:%M:%S") + new_time = datetime.strptime(time2, "%H:%M:%S") + time_difference = new_time - original_time + time_difference_seconds = time_difference.total_seconds() + return int(time_difference_seconds) + + +def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + current_time = OutputText(page, "current_time") + current_time.expect_value(re.compile(r"\d{2}:\d{2}:\d{2}")) + + # extended task + button1 = InputTaskButton(page, "btn") + click_button_and_assert_time_difference(button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."]) + + # extended task with blocking + button2 = InputTaskButton(page, "btn2") + click_button_and_assert_time_difference(button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."]) + From 1493f2ac50b6b420051db73600e2d57072de9b54 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 09:36:32 -0800 Subject: [PATCH 02/24] Verify the sum calculation takes place --- .../test_input_task_button.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 7a61eb5db..773383c7a 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -3,7 +3,7 @@ from datetime import datetime from conftest import ShinyAppProc -from controls import InputTaskButton, OutputText +from controls import InputTaskButton, OutputText, InputNumeric from playwright.sync_api import Page @@ -11,7 +11,7 @@ def click_button_and_assert_time_difference( button: InputTaskButton, current_time: OutputText, button_label: list[str], -) -> int: +) -> float: button.expect_state("ready") button.expect_label_text(button_label) time1 = current_time.get_value() @@ -23,19 +23,26 @@ def click_button_and_assert_time_difference( new_time = datetime.strptime(time2, "%H:%M:%S") time_difference = new_time - original_time time_difference_seconds = time_difference.total_seconds() - return int(time_difference_seconds) + return time_difference_seconds def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) + y = InputNumeric(page, "y") + y.set("4") + result = OutputText(page, "show_result") current_time = OutputText(page, "current_time") current_time.expect_value(re.compile(r"\d{2}:\d{2}:\d{2}")) # extended task button1 = InputTaskButton(page, "btn") - click_button_and_assert_time_difference(button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."]) + time_diff = click_button_and_assert_time_difference(button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."]) + assert time_diff > 1 + + time.sleep(2) + assert int(result.get_value()) == 5 # extended task with blocking button2 = InputTaskButton(page, "btn2") - click_button_and_assert_time_difference(button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."]) - + time_diff = click_button_and_assert_time_difference(button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."]) + assert time_diff < 1 From 53c19eafbe813d2bb60fb04474ce9aac1f331edf Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 12:36:41 -0800 Subject: [PATCH 03/24] Refactor test to reduce sleep times --- .../shiny/inputs/input_task_button/app.py | 6 +-- .../test_input_task_button.py | 46 +++++++++---------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/app.py b/tests/playwright/shiny/inputs/input_task_button/app.py index 0b0c86d0e..6af0e05e0 100644 --- a/tests/playwright/shiny/inputs/input_task_button/app.py +++ b/tests/playwright/shiny/inputs/input_task_button/app.py @@ -9,8 +9,8 @@ @render.text() def current_time(): - reactive.invalidate_later(1) - return datetime.now().strftime("%H:%M:%S") + reactive.invalidate_later(0.1) + return datetime.now().utcnow() with ui.p(): @@ -48,7 +48,7 @@ def handle_click(): @reactive.event(input.btn2, ignore_none=False) async def handle_click2(): # slow_compute.cancel() - val = await slow_input_compute(input.x(), input.y()) + await slow_input_compute(input.x(), input.y()) @reactive.Effect @reactive.event(input.btn_cancel) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 773383c7a..80dffb09b 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -1,29 +1,18 @@ -import re -import time -from datetime import datetime - from conftest import ShinyAppProc -from controls import InputTaskButton, OutputText, InputNumeric +from controls import InputNumeric, InputTaskButton, OutputText from playwright.sync_api import Page -def click_button_and_assert_time_difference( +def click_extended_task_button( button: InputTaskButton, current_time: OutputText, button_label: list[str], -) -> float: +) -> str: button.expect_state("ready") button.expect_label_text(button_label) - time1 = current_time.get_value() - button.click() - time.sleep(1.5) - button.expect_state("busy") - time2 = current_time.get_value() - original_time = datetime.strptime(time1, "%H:%M:%S") - new_time = datetime.strptime(time2, "%H:%M:%S") - time_difference = new_time - original_time - time_difference_seconds = time_difference.total_seconds() - return time_difference_seconds + button.click(timeout=0) + button.expect_state("busy", timeout=0) + return current_time.get_value(timeout=0) def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: @@ -32,17 +21,26 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: y.set("4") result = OutputText(page, "show_result") current_time = OutputText(page, "current_time") - current_time.expect_value(re.compile(r"\d{2}:\d{2}:\d{2}")) + current_time.expect.not_to_be_empty() + + result.expect_value("3") # extended task button1 = InputTaskButton(page, "btn") - time_diff = click_button_and_assert_time_difference(button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."]) - assert time_diff > 1 + time1 = click_extended_task_button( + button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."] + ) + current_time.expect.not_to_have_text(time1, timeout=500) - time.sleep(2) - assert int(result.get_value()) == 5 + result.expect_value("3", timeout=0) + result.expect_value("5", timeout=(3 + 1) * 1000) + y.set("15") + result.expect_value("5") # extended task with blocking button2 = InputTaskButton(page, "btn2") - time_diff = click_button_and_assert_time_difference(button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."]) - assert time_diff < 1 + time2 = click_extended_task_button( + button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."] + ) + # page.wait_for_timeout(500) + current_time.expect_value(time2, timeout=0) From df9266b9663ce515a6b3b910d4453eacec20d309 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 14:07:29 -0800 Subject: [PATCH 04/24] fix pyright issues --- tests/playwright/shiny/inputs/input_task_button/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/app.py b/tests/playwright/shiny/inputs/input_task_button/app.py index 6af0e05e0..e3b55e682 100644 --- a/tests/playwright/shiny/inputs/input_task_button/app.py +++ b/tests/playwright/shiny/inputs/input_task_button/app.py @@ -8,9 +8,9 @@ @render.text() -def current_time(): +def current_time() -> str: reactive.invalidate_later(0.1) - return datetime.now().utcnow() + return str(datetime.now().utcnow()) with ui.p(): From 35ea991930088fa38f4ed8b1c8fe8e0701449828 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 14:09:54 -0800 Subject: [PATCH 05/24] linting issues --- tests/playwright/controls.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index a60fc3b60..8b5c94cd3 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -816,11 +816,17 @@ def __init__( ) def expect_state(self, state: str, *, timeout: Timeout = None): - expect_attr(self.loc.locator("> bslib-switch-inline"), name="case", value=state, timeout=timeout) + expect_attr( + self.loc.locator("> bslib-switch-inline"), + name="case", + value=state, + timeout=timeout, + ) def expect_label_text(self, value: list[str], *, timeout: Timeout = None): - playwright_expect(self.loc.locator("> bslib-switch-inline > span")).to_have_text(value, timeout=timeout) - + playwright_expect( + self.loc.locator("> bslib-switch-inline > span") + ).to_have_text(value, timeout=timeout) class InputActionLink(_InputActionBase): @@ -2206,7 +2212,6 @@ def get_value(self, *, timeout: Timeout = None) -> str: return self.loc.inner_text(timeout=timeout) - class OutputCode(_OutputTextValue): def __init__(self, page: Page, id: str) -> None: super().__init__(page, id=id, loc=f"pre#{id}.shiny-text-output") From 211bb70b82c73e1313167f9f25ac187fe9198eea Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 14:10:48 -0800 Subject: [PATCH 06/24] remove comments --- .../shiny/inputs/input_task_button/test_input_task_button.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 80dffb09b..ff354b2ef 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -42,5 +42,4 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: time2 = click_extended_task_button( button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."] ) - # page.wait_for_timeout(500) current_time.expect_value(time2, timeout=0) From b21fc34a0bb55cb0d3bb0099028b1bc123636ccf Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 14:23:18 -0800 Subject: [PATCH 07/24] fix pyright issues --- .../shiny/inputs/input_task_button/test_input_task_button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index ff354b2ef..9be244b26 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -6,7 +6,7 @@ def click_extended_task_button( button: InputTaskButton, current_time: OutputText, - button_label: list[str], + button_label: list["str"], ) -> str: button.expect_state("ready") button.expect_label_text(button_label) From 712812d78995b3b7aa74edcc380afb40f7896293 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 14:31:32 -0800 Subject: [PATCH 08/24] see if this import fixes pyright error --- .../shiny/inputs/input_task_button/test_input_task_button.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 9be244b26..5cd2bb8b6 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -1,3 +1,4 @@ +from __future__ import annotations from conftest import ShinyAppProc from controls import InputNumeric, InputTaskButton, OutputText from playwright.sync_api import Page @@ -6,7 +7,7 @@ def click_extended_task_button( button: InputTaskButton, current_time: OutputText, - button_label: list["str"], + button_label: list[str], ) -> str: button.expect_state("ready") button.expect_label_text(button_label) From 5ac19b4b63ff0e2f2b52506601cc907c73d01b93 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Thu, 1 Feb 2024 14:42:31 -0800 Subject: [PATCH 09/24] imports are incorrectly sorted --- .../shiny/inputs/input_task_button/test_input_task_button.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 5cd2bb8b6..fe42ed6f3 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -1,4 +1,5 @@ from __future__ import annotations + from conftest import ShinyAppProc from controls import InputNumeric, InputTaskButton, OutputText from playwright.sync_api import Page From 80f3e009e0a49fc447455b16ea81e50a050ad514 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:38:10 -0800 Subject: [PATCH 10/24] Update shiny/api-examples/extended_task/app-core.py Co-authored-by: Barret Schloerke --- shiny/api-examples/extended_task/app-core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/api-examples/extended_task/app-core.py b/shiny/api-examples/extended_task/app-core.py index b141a9b3d..3a8cee88d 100644 --- a/shiny/api-examples/extended_task/app-core.py +++ b/shiny/api-examples/extended_task/app-core.py @@ -25,7 +25,7 @@ async def slow_compute(a: int, b: int) -> int: # @ui.bind_task_button(button_id="btn2") -async def slow_input_compute(a: int, b: int) -> int: +async def blocking_input_compute(a: int, b: int) -> int: await asyncio.sleep(3) return a + b From 1514d7fda71a39a37caf969f0866d7113b378210 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:38:19 -0800 Subject: [PATCH 11/24] Update shiny/api-examples/extended_task/app-core.py Co-authored-by: Barret Schloerke --- shiny/api-examples/extended_task/app-core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shiny/api-examples/extended_task/app-core.py b/shiny/api-examples/extended_task/app-core.py index 3a8cee88d..d380519df 100644 --- a/shiny/api-examples/extended_task/app-core.py +++ b/shiny/api-examples/extended_task/app-core.py @@ -24,7 +24,6 @@ async def slow_compute(a: int, b: int) -> int: return a + b -# @ui.bind_task_button(button_id="btn2") async def blocking_input_compute(a: int, b: int) -> int: await asyncio.sleep(3) return a + b From a428c3272caf060b0f18de37dafec30d9ffb40df Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:38:32 -0800 Subject: [PATCH 12/24] Update shiny/api-examples/extended_task/app-core.py Co-authored-by: Barret Schloerke --- shiny/api-examples/extended_task/app-core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/api-examples/extended_task/app-core.py b/shiny/api-examples/extended_task/app-core.py index d380519df..68b01e793 100644 --- a/shiny/api-examples/extended_task/app-core.py +++ b/shiny/api-examples/extended_task/app-core.py @@ -34,7 +34,7 @@ async def blocking_input_compute(a: int, b: int) -> int: ui.input_numeric("x", "x", 1) ui.input_numeric("y", "y", 2) ui.input_task_button("btn", "Compute, slowly") - ui.input_task_button("btn2", "Compute 2 slowly") + ui.input_task_button("btn_block", "Block timer") ui.input_action_button("btn_cancel", "Cancel") @reactive.Effect From 6588eefd7702f31199c38d6e6c8cba4f63472900 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:38:41 -0800 Subject: [PATCH 13/24] Update shiny/api-examples/extended_task/app-core.py Co-authored-by: Barret Schloerke --- shiny/api-examples/extended_task/app-core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shiny/api-examples/extended_task/app-core.py b/shiny/api-examples/extended_task/app-core.py index 68b01e793..78e5973fe 100644 --- a/shiny/api-examples/extended_task/app-core.py +++ b/shiny/api-examples/extended_task/app-core.py @@ -33,7 +33,7 @@ async def blocking_input_compute(a: int, b: int) -> int: with ui.sidebar(): ui.input_numeric("x", "x", 1) ui.input_numeric("y", "y", 2) - ui.input_task_button("btn", "Compute, slowly") + ui.input_task_button("btn_task", "Compute task") ui.input_task_button("btn_block", "Block timer") ui.input_action_button("btn_cancel", "Cancel") From 59a5d68f4cb4cca34427ed7d740a13d3ad64f59f Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:38:58 -0800 Subject: [PATCH 14/24] Update tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py Co-authored-by: Barret Schloerke --- .../shiny/inputs/input_task_button/test_input_task_button.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index fe42ed6f3..e1104e985 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -25,6 +25,7 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: current_time = OutputText(page, "current_time") current_time.expect.not_to_be_empty() + # Wait until shiny is stable result.expect_value("3") # extended task From 47e584875b6c1729e61af7f2b93ab8d0c7a80b84 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:39:08 -0800 Subject: [PATCH 15/24] Update tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py Co-authored-by: Barret Schloerke --- .../shiny/inputs/input_task_button/test_input_task_button.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index e1104e985..50e2f07fe 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -23,6 +23,7 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: y.set("4") result = OutputText(page, "show_result") current_time = OutputText(page, "current_time") + # Make sure the time has content current_time.expect.not_to_be_empty() # Wait until shiny is stable From ab5f9b45ddbc46e14fabcf1ebc3759d76664d3e5 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:39:19 -0800 Subject: [PATCH 16/24] Update tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py Co-authored-by: Barret Schloerke --- .../shiny/inputs/input_task_button/test_input_task_button.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 50e2f07fe..d35a5723b 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -29,11 +29,13 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: # Wait until shiny is stable result.expect_value("3") - # extended task + # Extended task button1 = InputTaskButton(page, "btn") + # Click button and collect the current time from the app time1 = click_extended_task_button( button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."] ) + # Make sure time value updates (before the calculation finishes current_time.expect.not_to_have_text(time1, timeout=500) result.expect_value("3", timeout=0) From bc677daac7233a7b4ce86ab2f40cb7fef5919218 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:39:27 -0800 Subject: [PATCH 17/24] Update tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py Co-authored-by: Barret Schloerke --- .../shiny/inputs/input_task_button/test_input_task_button.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index d35a5723b..dea8c069f 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -37,9 +37,11 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: ) # Make sure time value updates (before the calculation finishes current_time.expect.not_to_have_text(time1, timeout=500) - result.expect_value("3", timeout=0) + # After the calculation time plus a buffer, make sure the calculation finishes result.expect_value("5", timeout=(3 + 1) * 1000) + + # set up Blocking test y.set("15") result.expect_value("5") From 68fcc61d46ee36daea3ea442a8abf9bbd4b77a5e Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 09:39:40 -0800 Subject: [PATCH 18/24] Update tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py Co-authored-by: Barret Schloerke --- .../input_task_button/test_input_task_button.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index dea8c069f..05529f512 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -45,9 +45,12 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: y.set("15") result.expect_value("5") - # extended task with blocking - button2 = InputTaskButton(page, "btn2") - time2 = click_extended_task_button( - button2, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."] + # Blocking verification + button_block = InputTaskButton(page, "btn_block") + time_block = click_extended_task_button( + button_block, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."] ) - current_time.expect_value(time2, timeout=0) + # Make sure time value has not changed after 500ms has ellapsed + import time # TODO-karan: move this line to the top + time.sleep(0.5) + current_time.expect_value(time_block, timeout=0) From 6219c1610f8b2f07715ae5f108a1cc58be9fb552 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Fri, 2 Feb 2024 09:40:59 -0800 Subject: [PATCH 19/24] revert changes to app.py in examples --- shiny/api-examples/extended_task/app-core.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/shiny/api-examples/extended_task/app-core.py b/shiny/api-examples/extended_task/app-core.py index 78e5973fe..6fa9cbe8b 100644 --- a/shiny/api-examples/extended_task/app-core.py +++ b/shiny/api-examples/extended_task/app-core.py @@ -24,17 +24,11 @@ async def slow_compute(a: int, b: int) -> int: return a + b -async def blocking_input_compute(a: int, b: int) -> int: - await asyncio.sleep(3) - return a + b - - with ui.layout_sidebar(): with ui.sidebar(): ui.input_numeric("x", "x", 1) ui.input_numeric("y", "y", 2) - ui.input_task_button("btn_task", "Compute task") - ui.input_task_button("btn_block", "Block timer") + ui.input_task_button("btn", "Compute, slowly") ui.input_action_button("btn_cancel", "Cancel") @reactive.Effect @@ -43,13 +37,6 @@ def handle_click(): # slow_compute.cancel() slow_compute(input.x(), input.y()) - @reactive.Effect - @reactive.event(input.btn2, ignore_none=False) - async def handle_click2(): - # slow_compute.cancel() - val = await slow_input_compute(input.x(), input.y()) - print(val) - @reactive.Effect @reactive.event(input.btn_cancel) def handle_cancel(): From a3857511b1505690ec58343473ce1c846ea15be3 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Fri, 2 Feb 2024 09:49:01 -0800 Subject: [PATCH 20/24] change the id of button to btn_block --- tests/playwright/shiny/inputs/input_task_button/app.py | 5 ++--- .../inputs/input_task_button/test_input_task_button.py | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/playwright/shiny/inputs/input_task_button/app.py b/tests/playwright/shiny/inputs/input_task_button/app.py index e3b55e682..1769dae32 100644 --- a/tests/playwright/shiny/inputs/input_task_button/app.py +++ b/tests/playwright/shiny/inputs/input_task_button/app.py @@ -24,7 +24,6 @@ async def slow_compute(a: int, b: int) -> int: return a + b -# @ui.bind_task_button(button_id="btn2") async def slow_input_compute(a: int, b: int) -> int: await asyncio.sleep(3) return a + b @@ -35,7 +34,7 @@ async def slow_input_compute(a: int, b: int) -> int: ui.input_numeric("x", "x", 1) ui.input_numeric("y", "y", 2) ui.input_task_button("btn", "Compute, slowly") - ui.input_task_button("btn2", "Compute 2 slowly", label_busy="Blocking...") + ui.input_task_button("btn_block", "Compute 2 slowly", label_busy="Blocking...") ui.input_action_button("btn_cancel", "Cancel") @reactive.Effect @@ -45,7 +44,7 @@ def handle_click(): slow_compute(input.x(), input.y()) @reactive.Effect - @reactive.event(input.btn2, ignore_none=False) + @reactive.event(input.btn_block, ignore_none=False) async def handle_click2(): # slow_compute.cancel() await slow_input_compute(input.x(), input.y()) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 05529f512..9dc73c1dd 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -1,5 +1,7 @@ from __future__ import annotations +import time + from conftest import ShinyAppProc from controls import InputNumeric, InputTaskButton, OutputText from playwright.sync_api import Page @@ -48,9 +50,10 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: # Blocking verification button_block = InputTaskButton(page, "btn_block") time_block = click_extended_task_button( - button_block, current_time, button_label=["Compute 2 slowly", "\n \n Blocking..."] + button_block, + current_time, + button_label=["Compute 2 slowly", "\n \n Blocking..."], ) # Make sure time value has not changed after 500ms has ellapsed - import time # TODO-karan: move this line to the top time.sleep(0.5) current_time.expect_value(time_block, timeout=0) From b55df775e135722ed4e1aa105d80a42e750aaf87 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Fri, 2 Feb 2024 11:23:30 -0800 Subject: [PATCH 21/24] Mark accordion test as flaky --- tests/playwright/shiny/components/accordion/test_accordion.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/playwright/shiny/components/accordion/test_accordion.py b/tests/playwright/shiny/components/accordion/test_accordion.py index 1345d3a83..f47786728 100644 --- a/tests/playwright/shiny/components/accordion/test_accordion.py +++ b/tests/playwright/shiny/components/accordion/test_accordion.py @@ -1,8 +1,11 @@ +import pytest from conftest import ShinyAppProc from controls import Accordion, InputActionButton, OutputTextVerbatim +from examples.example_apps import reruns, reruns_delay from playwright.sync_api import Page +@pytest.mark.flaky(reruns=reruns, reruns_delay=reruns_delay) def test_accordion(page: Page, local_app: ShinyAppProc) -> None: page.goto(local_app.url) From 389c64a408e96ce22b9364e786f9bec5be7ab893 Mon Sep 17 00:00:00 2001 From: Karan Date: Fri, 2 Feb 2024 12:04:05 -0800 Subject: [PATCH 22/24] Update tests/playwright/controls.py Co-authored-by: Barret Schloerke --- tests/playwright/controls.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index f6a6d176b..a92d315da 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -823,7 +823,10 @@ def expect_state(self, state: str, *, timeout: Timeout = None): timeout=timeout, ) - def expect_label_text(self, value: list[str], *, timeout: Timeout = None): + def expect_label(self, value: str, *, timeout: Timeout = None): + raise NotImplemented("Please use expect_labels()") + + def expect_labels(self, value: list[str], *, timeout: Timeout = None): playwright_expect( self.loc.locator("> bslib-switch-inline > span") ).to_have_text(value, timeout=timeout) From e2869063ef6e97512284adf41382e27dcb028846 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Fri, 2 Feb 2024 12:53:54 -0800 Subject: [PATCH 23/24] Change the name of the task buttons --- tests/playwright/controls.py | 67 +++++++++++++------ .../shiny/inputs/input_task_button/app.py | 12 ++-- .../test_input_task_button.py | 16 +++-- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index a92d315da..cb1be3d9b 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -1,4 +1,5 @@ """Facade classes for working with Shiny inputs/outputs in Playwright""" + from __future__ import annotations import json @@ -794,6 +795,7 @@ class InputTaskButton( _WidthLocM, _InputActionBase, ): + # TODO-Karan: Test auto_reset functionality # id: str, # label: TagChild, # *args: TagChild, @@ -815,22 +817,37 @@ def __init__( loc=f"button#{id}.bslib-task-button.shiny-bound-input", ) - def expect_state(self, state: str, *, timeout: Timeout = None): + def expect_state(self, value: Literal["ready", "busy"] | str, *, timeout: Timeout = None): expect_attr( self.loc.locator("> bslib-switch-inline"), name="case", - value=state, + value=value, timeout=timeout, ) - def expect_label(self, value: str, *, timeout: Timeout = None): - raise NotImplemented("Please use expect_labels()") - - def expect_labels(self, value: list[str], *, timeout: Timeout = None): + def expect_label(self, value: PatternOrStr, *, timeout: Timeout = None) -> None: + self.expect_label_ready(value, timeout=timeout) + + def expect_label_ready(self, value: PatternOrStr, *, timeout: Timeout = None): + self.expect_label_state("ready", value, timeout=timeout) + + def expect_label_busy(self, value: PatternOrStr, *, timeout: Timeout = None): + self.expect_label_state("busy", value, timeout=timeout) + + def expect_label_state( + self, state: str, value: PatternOrStr, *, timeout: Timeout = None + ): playwright_expect( - self.loc.locator("> bslib-switch-inline > span") + self.loc.locator(f"> bslib-switch-inline > span[slot='{state}']") ).to_have_text(value, timeout=timeout) + def expect_auto_reset(self, value: bool, timeout: Timeout = None): + expect_attr( + self.loc, + name="data-auto-reset", + value="" if value else None, + timeout=timeout, + ) class InputActionLink(_InputActionBase): # label: TagChild, @@ -1351,11 +1368,13 @@ def __init__( def set( self, - file_path: str - | pathlib.Path - | FilePayload - | list[str | pathlib.Path] - | list[FilePayload], + file_path: ( + str + | pathlib.Path + | FilePayload + | list[str | pathlib.Path] + | list[FilePayload] + ), *, timeout: Timeout = None, expect_complete_timeout: Timeout = 30 * 1000, @@ -1710,9 +1729,11 @@ def __init__( def expect_value( self, - value: typing.Tuple[PatternOrStr, PatternOrStr] - | typing.Tuple[PatternOrStr, MISSING_TYPE] - | typing.Tuple[MISSING_TYPE, PatternOrStr], + value: ( + typing.Tuple[PatternOrStr, PatternOrStr] + | typing.Tuple[PatternOrStr, MISSING_TYPE] + | typing.Tuple[MISSING_TYPE, PatternOrStr] + ), *, timeout: Timeout = None, ) -> None: @@ -1755,9 +1776,11 @@ def _set_fraction( def set( self, - value: typing.Tuple[str, str] - | typing.Tuple[str, MISSING_TYPE] - | typing.Tuple[MISSING_TYPE, str], + value: ( + typing.Tuple[str, str] + | typing.Tuple[str, MISSING_TYPE] + | typing.Tuple[MISSING_TYPE, str] + ), *, max_err_values: int = 15, timeout: Timeout = None, @@ -2016,9 +2039,11 @@ def set( def expect_value( self, - value: typing.Tuple[PatternOrStr, PatternOrStr] - | typing.Tuple[PatternOrStr, MISSING_TYPE] - | typing.Tuple[MISSING_TYPE, PatternOrStr], + value: ( + typing.Tuple[PatternOrStr, PatternOrStr] + | typing.Tuple[PatternOrStr, MISSING_TYPE] + | typing.Tuple[MISSING_TYPE, PatternOrStr] + ), *, timeout: Timeout = None, ) -> None: diff --git a/tests/playwright/shiny/inputs/input_task_button/app.py b/tests/playwright/shiny/inputs/input_task_button/app.py index 1769dae32..edd483f9e 100644 --- a/tests/playwright/shiny/inputs/input_task_button/app.py +++ b/tests/playwright/shiny/inputs/input_task_button/app.py @@ -17,15 +17,15 @@ def current_time() -> str: "Notice that the time above updates every second, even if you click the button below." -@ui.bind_task_button(button_id="btn") +@ui.bind_task_button(button_id="btn_task") @reactive.extended_task async def slow_compute(a: int, b: int) -> int: - await asyncio.sleep(3) + await asyncio.sleep(1.5) return a + b async def slow_input_compute(a: int, b: int) -> int: - await asyncio.sleep(3) + await asyncio.sleep(1.5) return a + b @@ -33,12 +33,12 @@ async def slow_input_compute(a: int, b: int) -> int: with ui.sidebar(): ui.input_numeric("x", "x", 1) ui.input_numeric("y", "y", 2) - ui.input_task_button("btn", "Compute, slowly") - ui.input_task_button("btn_block", "Compute 2 slowly", label_busy="Blocking...") + ui.input_task_button("btn_task", "Non-blocking task") + ui.input_task_button("btn_block", "Block compute", label_busy="Blocking...") ui.input_action_button("btn_cancel", "Cancel") @reactive.Effect - @reactive.event(input.btn, ignore_none=False) + @reactive.event(input.btn_task, ignore_none=False) def handle_click(): # slow_compute.cancel() slow_compute(input.x(), input.y()) diff --git a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py index 9dc73c1dd..f90ce3d5b 100644 --- a/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py +++ b/tests/playwright/shiny/inputs/input_task_button/test_input_task_button.py @@ -10,10 +10,8 @@ def click_extended_task_button( button: InputTaskButton, current_time: OutputText, - button_label: list[str], ) -> str: button.expect_state("ready") - button.expect_label_text(button_label) button.click(timeout=0) button.expect_state("busy", timeout=0) return current_time.get_value(timeout=0) @@ -32,16 +30,20 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: result.expect_value("3") # Extended task - button1 = InputTaskButton(page, "btn") + button_task = InputTaskButton(page, "btn_task") + button_task.expect_label_busy("\n \n Processing...") + button_task.expect_label_ready("Non-blocking task") + button_task.expect_auto_reset(True) # Click button and collect the current time from the app time1 = click_extended_task_button( - button1, current_time, button_label=["Compute, slowly", "\n \n Processing..."] + button_task, + current_time, ) # Make sure time value updates (before the calculation finishes current_time.expect.not_to_have_text(time1, timeout=500) result.expect_value("3", timeout=0) # After the calculation time plus a buffer, make sure the calculation finishes - result.expect_value("5", timeout=(3 + 1) * 1000) + result.expect_value("5", timeout=(1.5 + 1) * 1000) # set up Blocking test y.set("15") @@ -49,10 +51,12 @@ def test_input_action_task_button(page: Page, local_app: ShinyAppProc) -> None: # Blocking verification button_block = InputTaskButton(page, "btn_block") + button_block.expect_label_busy("\n \n Blocking...") + button_block.expect_label_ready("Block compute") + button_block.expect_auto_reset(True) time_block = click_extended_task_button( button_block, current_time, - button_label=["Compute 2 slowly", "\n \n Blocking..."], ) # Make sure time value has not changed after 500ms has ellapsed time.sleep(0.5) From 0bcea668f891d3b0153f3c7f038aefa1a46abed5 Mon Sep 17 00:00:00 2001 From: Karan Gathani Date: Fri, 2 Feb 2024 13:08:41 -0800 Subject: [PATCH 24/24] Linting errors --- tests/playwright/controls.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/playwright/controls.py b/tests/playwright/controls.py index cb1be3d9b..cdce59212 100644 --- a/tests/playwright/controls.py +++ b/tests/playwright/controls.py @@ -817,7 +817,9 @@ def __init__( loc=f"button#{id}.bslib-task-button.shiny-bound-input", ) - def expect_state(self, value: Literal["ready", "busy"] | str, *, timeout: Timeout = None): + def expect_state( + self, value: Literal["ready", "busy"] | str, *, timeout: Timeout = None + ): expect_attr( self.loc.locator("> bslib-switch-inline"), name="case", @@ -849,6 +851,7 @@ def expect_auto_reset(self, value: bool, timeout: Timeout = None): timeout=timeout, ) + class InputActionLink(_InputActionBase): # label: TagChild, # icon: TagChild = None,