Skip to content

Commit

Permalink
Test input task button and extended task decorator (#1099)
Browse files Browse the repository at this point in the history
Co-authored-by: Barret Schloerke <barret@posit.co>
  • Loading branch information
karangattu and schloerke committed Feb 2, 2024
1 parent a3389df commit 6f4bf63
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 14 deletions.
101 changes: 87 additions & 14 deletions tests/playwright/controls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Facade classes for working with Shiny inputs/outputs in Playwright"""

from __future__ import annotations

import json
Expand Down Expand Up @@ -790,6 +791,67 @@ def __init__(
)


class InputTaskButton(
_WidthLocM,
_InputActionBase,
):
# TODO-Karan: Test auto_reset functionality
# 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, value: Literal["ready", "busy"] | str, *, timeout: Timeout = None
):
expect_attr(
self.loc.locator("> bslib-switch-inline"),
name="case",
value=value,
timeout=timeout,
)

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(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,
# icon: TagChild = None,
Expand Down Expand Up @@ -1309,11 +1371,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,
Expand Down Expand Up @@ -1668,9 +1732,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:
Expand Down Expand Up @@ -1713,9 +1779,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,
Expand Down Expand Up @@ -1974,9 +2042,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:
Expand Down Expand Up @@ -2169,6 +2239,9 @@ 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)


class OutputCode(_OutputTextValue):
def __init__(self, page: Page, id: str) -> None:
Expand Down
3 changes: 3 additions & 0 deletions tests/playwright/shiny/components/accordion/test_accordion.py
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
61 changes: 61 additions & 0 deletions tests/playwright/shiny/inputs/input_task_button/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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() -> str:
reactive.invalidate_later(0.1)
return str(datetime.now().utcnow())


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_task")
@reactive.extended_task
async def slow_compute(a: int, b: int) -> int:
await asyncio.sleep(1.5)
return a + b


async def slow_input_compute(a: int, b: int) -> int:
await asyncio.sleep(1.5)
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", "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_task, ignore_none=False)
def handle_click():
# slow_compute.cancel()
slow_compute(input.x(), input.y())

@reactive.Effect
@reactive.event(input.btn_block, ignore_none=False)
async def handle_click2():
# slow_compute.cancel()
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())
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

import time

from conftest import ShinyAppProc
from controls import InputNumeric, InputTaskButton, OutputText
from playwright.sync_api import Page


def click_extended_task_button(
button: InputTaskButton,
current_time: OutputText,
) -> str:
button.expect_state("ready")
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:
page.goto(local_app.url)
y = InputNumeric(page, "y")
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
result.expect_value("3")

# Extended task
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(
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=(1.5 + 1) * 1000)

# set up Blocking test
y.set("15")
result.expect_value("5")

# 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,
)
# Make sure time value has not changed after 500ms has ellapsed
time.sleep(0.5)
current_time.expect_value(time_block, timeout=0)

0 comments on commit 6f4bf63

Please sign in to comment.