Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test input task button and extended task decorator #1099

Merged
merged 26 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5d4b87b
add test for input task button
karangattu Jan 30, 2024
5dbb53b
Merge branch 'main' into test-extended-task
karangattu Feb 1, 2024
1493f2a
Verify the sum calculation takes place
karangattu Feb 1, 2024
53c19ea
Refactor test to reduce sleep times
karangattu Feb 1, 2024
df9266b
fix pyright issues
karangattu Feb 1, 2024
35ea991
linting issues
karangattu Feb 1, 2024
211bb70
remove comments
karangattu Feb 1, 2024
b21fc34
fix pyright issues
karangattu Feb 1, 2024
712812d
see if this import fixes pyright error
karangattu Feb 1, 2024
5ac19b4
imports are incorrectly sorted
karangattu Feb 1, 2024
80f3e00
Update shiny/api-examples/extended_task/app-core.py
karangattu Feb 2, 2024
1514d7f
Update shiny/api-examples/extended_task/app-core.py
karangattu Feb 2, 2024
a428c32
Update shiny/api-examples/extended_task/app-core.py
karangattu Feb 2, 2024
6588eef
Update shiny/api-examples/extended_task/app-core.py
karangattu Feb 2, 2024
59a5d68
Update tests/playwright/shiny/inputs/input_task_button/test_input_tas…
karangattu Feb 2, 2024
47e5848
Update tests/playwright/shiny/inputs/input_task_button/test_input_tas…
karangattu Feb 2, 2024
ab5f9b4
Update tests/playwright/shiny/inputs/input_task_button/test_input_tas…
karangattu Feb 2, 2024
bc677da
Update tests/playwright/shiny/inputs/input_task_button/test_input_tas…
karangattu Feb 2, 2024
68fcc61
Update tests/playwright/shiny/inputs/input_task_button/test_input_tas…
karangattu Feb 2, 2024
6219c16
revert changes to app.py in examples
karangattu Feb 2, 2024
a385751
change the id of button to btn_block
karangattu Feb 2, 2024
538641c
Merge branch 'main' into test-extended-task
karangattu Feb 2, 2024
b55df77
Mark accordion test as flaky
karangattu Feb 2, 2024
389c64a
Update tests/playwright/controls.py
karangattu Feb 2, 2024
e286906
Change the name of the task buttons
karangattu Feb 2, 2024
0bcea66
Linting errors
karangattu Feb 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
schloerke marked this conversation as resolved.
Show resolved Hide resolved
# label_busy: TagChild = "Processing...",
# icon_busy: TagChild | MISSING_TYPE = MISSING,
# width: Optional[str] = None,
# type: Optional[str] = "primary",
# auto_reset: bool = True,
schloerke marked this conversation as resolved.
Show resolved Hide resolved
# **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()
karangattu marked this conversation as resolved.
Show resolved Hide resolved

# Wait until shiny is stable
result.expect_value("3")
karangattu marked this conversation as resolved.
Show resolved Hide resolved

# 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)
Loading