diff --git a/docs/.gitignore b/docs/.gitignore index b65bacee6..f2af3e74d 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -5,5 +5,4 @@ _sidebar.yml /.quarto/ objects.json site_libs/ -_objects_core.json -_objects_express.json +_objects_*.json diff --git a/docs/Makefile b/docs/Makefile index 4dbb4e1fa..ce5c6c74b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -49,7 +49,7 @@ deps: $(PYBIN) dev-htmltools dev-shinylive ## Install build dependencies $(PYBIN)/pip install pip --upgrade $(PYBIN)/pip install ..[doc] -quartodoc: quartodoc_build_core quartodoc_build_express quartodoc_post ## Build quartodocs for express and core +quartodoc: quartodoc_build_core quartodoc_build_express quartodoc_build_test quartodoc_post ## Build quartodocs for express and core ## Build interlinks for API docs quartodoc_interlinks: $(PYBIN) @@ -78,6 +78,14 @@ quartodoc_build_express: $(PYBIN) quartodoc_interlinks && mv objects.json _objects_express.json \ && echo "::endgroup::" +## Build test API docs +quartodoc_build_test: $(PYBIN) quartodoc_interlinks + . $(PYBIN)/activate \ + && echo "::group::quartodoc build testing docs" \ + && quartodoc build --config _quartodoc-testing.yml --verbose \ + && mv objects.json _objects_test.json \ + && echo "::endgroup::" + ## Clean up after quartodoc build quartodoc_post: $(PYBIN) . $(PYBIN)/activate \ diff --git a/docs/_combine_objects_json.py b/docs/_combine_objects_json.py index fbb2a2215..b5080ecce 100644 --- a/docs/_combine_objects_json.py +++ b/docs/_combine_objects_json.py @@ -40,10 +40,11 @@ def write_objects_file(objects: QuartodocObject, path: str) -> None: print("\nCombining objects json files...") objects_core = read_objects_file("_objects_core.json") objects_express = read_objects_file("_objects_express.json") +objects_test = read_objects_file("_objects_test.json") items_map: dict[str, QuartodocObjectItem] = {} -for item in [*objects_core.items, *objects_express.items]: +for item in [*objects_core.items, *objects_express.items, *objects_test.items]: if item.name in items_map: continue items_map[item.name] = item @@ -58,6 +59,7 @@ def write_objects_file(objects: QuartodocObject, path: str) -> None: print("Core:", objects_core.count) print("Express:", objects_express.count) +print("Testing:", objects_test.count) print("Combined:", objects_ret.count) # Save combined objects file info diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 2d6b65308..eabda5673 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -22,6 +22,8 @@ website: file: api/express/index.qmd - text: "Core API" file: api/core/index.qmd + - text: "Testing API" + file: api/test/index.qmd right: - icon: github href: https://github.com/posit-dev/py-shiny diff --git a/docs/_quartodoc-testing.yml b/docs/_quartodoc-testing.yml new file mode 100644 index 000000000..e9c7de237 --- /dev/null +++ b/docs/_quartodoc-testing.yml @@ -0,0 +1,93 @@ +quartodoc: + style: pkgdown + dir: api/testing + out_index: index.qmd + package: shiny + rewrite_all_pages: false + sidebar: api/testing/_sidebar.yml + dynamic: false + renderer: + style: _renderer.py + show_signature_annotations: false + sections: + - title: UI Layouts + desc: Methods for interacting with Shiny app multiple UI component controls. + contents: + - playwright.controls.Accordion + - playwright.controls.AccordionPanel + - playwright.controls.Card + - playwright.controls.Popover + - playwright.controls.Sidebar + - playwright.controls.Tooltip + - title: UI Inputs + desc: Methods for interacting with Shiny app input value controls. + contents: + - playwright.controls.InputActionLink + - playwright.controls.InputCheckbox + - playwright.controls.InputCheckboxGroup + - playwright.controls.InputDarkMode + - playwright.controls.InputDate + - playwright.controls.InputDateRange + - playwright.controls.InputFile + - playwright.controls.InputNumeric + - playwright.controls.InputPassword + - playwright.controls.InputRadioButtons + - playwright.controls.InputSelect + - playwright.controls.InputSelectize + - playwright.controls.InputSlider + - playwright.controls.InputSliderRange + - playwright.controls.InputSwitch + - playwright.controls.InputTaskButton + - playwright.controls.InputText + - playwright.controls.InputTextArea + - title: Value boxes + desc: Methods for interacting with Shiny app valuebox controls. + contents: + - playwright.controls.ValueBox + - title: Navigation (tab) panels + desc: Methods for interacting with Shiny app UI content controls. + contents: + - playwright.controls.NavItem + - playwright.controls.NavsetBar + - playwright.controls.NavsetCardPill + - playwright.controls.NavsetCardTab + - playwright.controls.NavsetCardUnderline + - playwright.controls.NavsetHidden + - playwright.controls.NavsetPill + - playwright.controls.NavsetPillList + - playwright.controls.NavsetTab + - playwright.controls.NavsetUnderline + - title: Upload and download + desc: Methods for interacting with Shiny app uploading and downloading controls. + contents: + - playwright.controls.DownloadButton + - playwright.controls.DownloadLink + - title: Rendering outputs + desc: Render output in a variety of ways. + contents: + - playwright.controls.OutputCode + - playwright.controls.OutputDataFrame + - playwright.controls.OutputImage + - playwright.controls.OutputPlot + - playwright.controls.OutputTable + - playwright.controls.OutputText + - playwright.controls.OutputTextVerbatim + - playwright.controls.OutputUi + - title: "Playwright Expect" + desc: "Methods for testing the state of a locator within a Shiny app." + contents: + - playwright.expect.expect_to_change + - playwright.expect.expect_attribute_to_have_value + - playwright.expect.expect_to_have_class + - playwright.expect.expect_not_to_have_class + - playwright.expect.expect_to_have_style + - title: "Pytest" + desc: "Fixtures used for testing Shiny apps with Pytest." + contents: + - pytest.create_app_fixture + - pytest.ScopeName + - title: "Run" + desc: "Methods for starting a local Shiny app in the background" + contents: + - run.run_shiny_app + - run.ShinyAppProc diff --git a/shiny/_main.py b/shiny/_main.py index cad3e0905..1581362df 100644 --- a/shiny/_main.py +++ b/shiny/_main.py @@ -503,6 +503,45 @@ def try_import_module(module: str) -> Optional[types.ModuleType]: } +@main.group(help="""Add files to enhance your Shiny app.""") +def add() -> None: + pass + + +@add.command( + help="""Add a test file for a specified Shiny app. + +Add an empty test file for a specified app. You will be prompted with a destination +folder. If you don't provide a destination folder, it will be added in the current +working directory based on the app name. + +After creating the shiny app file, you can use `pytest` to run the tests: + + pytest TEST_FILE +""" +) +@click.option( + "--app", + "-a", + type=str, + help="Please provide the path to the app file for which you want to create a test file.", +) +@click.option( + "--test-file", + "-t", + type=str, + help="Please provide the name of the test file you want to create. The basename of the test file should start with `test_` and be unique across all test files.", +) +# Param for app.py, param for test_name +def test( + app: Path | None, + test_file: Path | None, +) -> None: + from ._template_utils import add_test_file + + add_test_file(app_file=app, test_file=test_file) + + @main.command( help="""Create a Shiny application from a template. diff --git a/shiny/_template_utils.py b/shiny/_template_utils.py index e79047486..26e97d412 100644 --- a/shiny/_template_utils.py +++ b/shiny/_template_utils.py @@ -322,3 +322,111 @@ def rename_unlink(file_to_rename: str, file_to_delete: str, dir: Path = app_dir) (app_dir / "app-core.py").rename(app_dir / "app.py") return app_dir + + +def add_test_file( + *, + app_file: Path | None, + test_file: Path | None, +): + + if app_file is None: + + def path_exists(x: Path) -> bool | str: + if not isinstance(x, (str, Path)): + return False + if Path(x).is_dir(): + return "Please provide a file path to your Shiny app" + return Path(x).exists() or f"Shiny app file can not be found: {x}" + + app_file_val = questionary.path( + "Enter the path to the app file:", + default=build_path_string("app.py"), + validate=path_exists, + ).ask() + else: + app_file_val = app_file + # User quit early + if app_file_val is None: + sys.exit(1) + app_file = Path(app_file_val) + + if test_file is None: + + def path_does_not_exist(x: Path) -> bool | str: + if not isinstance(x, (str, Path)): + return False + if Path(x).is_dir(): + return "Please provide a file path for your test file." + if Path(x).exists(): + return "Test file already exists. Please provide a new file name." + if not Path(x).name.startswith("test_"): + return "Test file must start with 'test_'" + return True + + test_file_val = questionary.path( + "Enter the path to the test file:", + default=build_path_string( + os.path.relpath(app_file.parent / "tests" / "test_app.py", ".") + ), + validate=path_does_not_exist, + ).ask() + else: + test_file_val = test_file + + # User quit early + if test_file_val is None: + sys.exit(1) + test_file = Path(test_file_val) + + # Make sure app file exists + if not app_file.exists(): + raise FileExistsError("App file does not exist: ", test_file) + # Make sure output test file doesn't exist + if test_file.exists(): + raise FileExistsError("Test file already exists: ", test_file) + if not test_file.name.startswith("test_"): + return "Test file must start with 'test_'" + + # if app path directory is the same as the test file directory, use `local_app` + # otherwise, use `create_app_fixture` + is_same_dir = app_file.parent == test_file.parent + + test_name = test_file.name.replace(".py", "") + rel_path = os.path.relpath(app_file, test_file.parent) + + template = ( + f"""\ +from playwright.sync_api import Page + +from shiny.playwright.controls import +from shiny.run import ShinyAppProc + + +def {test_name}(page: Page, local_app: ShinyAppProc): + + page.goto(local_app.url) + # Add tests code here +""" + if is_same_dir + else f"""\ +from playwright.sync_api import Page + +from shiny.playwright.controls import +from shiny.pytest import create_app_fixture +from shiny.run import ShinyAppProc + +app = create_app_fixture("{rel_path}") + + +def {test_name}(page: Page, app: ShinyAppProc): + + page.goto(app.url) + # Add tests code here +""" + ) + # Make sure test file directory exists + test_file.parent.mkdir(parents=True, exist_ok=True) + + # Write template to test file + test_file.write_text(template) diff --git a/shiny/playwright/controls/__init__.py b/shiny/playwright/controls/__init__.py index 6345cdd66..a17b0eb52 100644 --- a/shiny/playwright/controls/__init__.py +++ b/shiny/playwright/controls/__init__.py @@ -1,5 +1,6 @@ from ._controls import ( Accordion, + AccordionPanel, Card, DownloadButton, DownloadLink, @@ -77,6 +78,7 @@ "ValueBox", "Card", "Accordion", + "AccordionPanel", "Sidebar", "Popover", "Tooltip", diff --git a/shiny/playwright/controls/_controls.py b/shiny/playwright/controls/_controls.py index 9b8e2cd48..53aa60cca 100644 --- a/shiny/playwright/controls/_controls.py +++ b/shiny/playwright/controls/_controls.py @@ -419,7 +419,7 @@ class InputNumeric( _WidthLocM, _UiWithLabel, ): - """Input numeric control for :func:`~shiny.ui.input_numeric`.""" + """Controller for :func:`shiny.ui.input_numeric`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -573,7 +573,7 @@ class InputText( _ExpectSpellcheckAttrM, _UiWithLabel, ): - """Input text control for :func:`~shiny.ui.input_text`.""" + """Controller for :func:`shiny.ui.input_text`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -599,7 +599,7 @@ class InputPassword( _ExpectPlaceholderAttrM, _UiWithLabel, ): - """Input password control for :func:`~shiny.ui.input_password`.""" + """Controller for :func:`shiny.ui.input_password`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -650,7 +650,7 @@ class InputTextArea( _ExpectSpellcheckAttrM, _UiWithLabel, ): - """Input text area control for :func:`~shiny.ui.input_text_area`.""" + """Controller for :func:`shiny.ui.input_text_area`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -990,7 +990,7 @@ def expect_size(self, value: AttrValue, *, timeout: Timeout = None) -> None: class InputSelect(_InputSelectBase): - """Input select control for :func:`~shiny.ui.input_select`.""" + """Controller for :func:`shiny.ui.input_select`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -1031,7 +1031,7 @@ def expect_selectize(self, value: bool, *, timeout: Timeout = None) -> None: class InputSelectize(_InputSelectBase): - """Input selectize control for :func:`~shiny.ui.input_selectize`.""" + """Controller for :func:`shiny.ui.input_selectize`.""" def __init__(self, page: Page, id: str) -> None: super().__init__( @@ -1079,7 +1079,7 @@ class InputActionButton( _WidthLocM, _InputActionBase, ): - """Input action button control for :func:`~shiny.ui.input_action_button`.""" + """Controller for :func:`shiny.ui.input_action_button`.""" def __init__( self, @@ -1104,7 +1104,7 @@ def __init__( class InputDarkMode(_UiBase): - """Input dark mode control for :func:`~shiny.ui.input_dark_mode`.""" + """Controller for :func:`shiny.ui.input_dark_mode`.""" def __init__( self, @@ -1193,7 +1193,7 @@ class InputTaskButton( _WidthLocM, _InputActionBase, ): - """Input task button control for :func:`~shiny.ui.input_task_button`.""" + """Controller for :func:`shiny.ui.input_task_button`.""" # TODO-Karan: Test auto_reset functionality def __init__( @@ -1322,7 +1322,7 @@ def expect_auto_reset(self, value: bool, timeout: Timeout = None): class InputActionLink(_InputActionBase): - """Input action link control for :func:`~shiny.ui.input_action_link`.""" + """Controller for :func:`shiny.ui.input_action_link`.""" def __init__( self, @@ -1427,7 +1427,7 @@ def expect_checked(self, value: bool, *, timeout: Timeout = None) -> None: class InputCheckbox(_InputCheckboxBase): - """Input checkbox control for :func:`~shiny.ui.input_checkbox`.""" + """Controller for :func:`shiny.ui.input_checkbox`.""" def __init__( self, @@ -1453,7 +1453,7 @@ def __init__( class InputSwitch(_InputCheckboxBase): - """Input switch control for :func:`~shiny.ui.input_switch`.""" + """Controller for :func:`shiny.ui.input_switch`.""" def __init__( self, @@ -1925,7 +1925,7 @@ class InputRadioButtons( _WidthContainerM, _RadioButtonCheckboxGroupBase, ): - """Input radio buttons control for :func:`~shiny.ui.input_radio_buttons`.""" + """Controller for :func:`shiny.ui.input_radio_buttons`.""" loc_selected: Locator """ @@ -2058,7 +2058,7 @@ class InputFile( # _ExpectPlaceholderAttrM, _UiWithLabel, ): - """Input file control for :func:`~shiny.ui.input_file`.""" + """Controller for :func:`shiny.ui.input_file`.""" loc_button: Locator """ @@ -2665,7 +2665,7 @@ def _handle_center( class InputSlider(_InputSliderBase): - """Input slider control for :func:`~shiny.ui.input_slider`.""" + """Controller for :func:`shiny.ui.input_slider`.""" loc_irs_label: Locator """ @@ -2740,7 +2740,7 @@ def set( class InputSliderRange(_InputSliderBase): - """Input slider range control for :func:`~shiny.ui.input_slider_range`.""" + """Controller for :func:`shiny.ui.input_slider_range`.""" loc_irs_label_from: Locator """ @@ -3150,7 +3150,7 @@ def __init__(self, page: Page, id: str) -> None: class InputDateRange(_WidthContainerM, _UiWithLabel): - """Input date range control for :func:`~shiny.ui.input_date_range`.""" + """Controller for :func:`shiny.ui.input_date_range`.""" loc_separator: Locator """ @@ -3565,7 +3565,7 @@ class OutputText( _OutputInlineContainerM, _OutputTextValue, ): - """Text output control for :func:`~shiny.ui.text_output`.""" + """Controller for :func:`shiny.ui.text_output`.""" loc: Locator """ @@ -3602,7 +3602,7 @@ def get_value(self, *, timeout: Timeout = None) -> str: class OutputCode(_OutputTextValue): - """Code output control for :func:`~shiny.ui.code_output`.""" + """Controller for :func:`shiny.ui.code_output`.""" loc: Locator """ @@ -3647,7 +3647,7 @@ def expect_has_placeholder( class OutputTextVerbatim(_OutputTextValue): - """Verbatim text output control for :func:`~shiny.ui.text_output_verbatim`.""" + """Controller for :func:`shiny.ui.text_output_verbatim`.""" loc: Locator """ @@ -3851,7 +3851,7 @@ def __init__(self, page: Page, id: str) -> None: class OutputPlot(_OutputImageBase): - """Plot output control for :func:`~shiny.ui.plot_output`.""" + """Controller for :func:`shiny.ui.plot_output`.""" loc: Locator """ @@ -3873,7 +3873,7 @@ def __init__(self, page: Page, id: str) -> None: class OutputUi(_OutputInlineContainerM, _OutputBase): - """UI output control for :func:`~shiny.ui.ui_output`.""" + """Controller for :func:`shiny.ui.ui_output`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -3922,7 +3922,7 @@ def expect_text(self, value: str, *, timeout: Timeout = None) -> None: # When making selectors, use `xpath` so that direct decendents can be checked class OutputTable(_OutputBase): - """Table output control for :func:`~shiny.ui.table_output`.""" + """Controller for :func:`shiny.ui.table_output`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -4073,7 +4073,7 @@ class Sidebar( _WidthLocM, _UiWithContainer, ): - """Sidebar control for func: `~shiny.ui.sidebar`.""" + """Controller for func: `shiny.ui.sidebar`.""" loc_container: Locator """ @@ -4375,7 +4375,7 @@ class ValueBox( _UiWithContainer, ): """ - Value Box control for :func:`~shiny.ui.value_box`. + Controller for :func:`shiny.ui.value_box`. """ loc: Locator @@ -4520,7 +4520,7 @@ class Card( _UiWithContainer, ): """ - Card control for :func:`~shiny.ui.card`. + Controller for :func:`shiny.ui.card`. """ loc_container: Locator @@ -4677,7 +4677,7 @@ class Accordion( _WidthLocM, _UiWithContainer, ): - """Accordion control for :func:`~shiny.ui.accordion`.""" + """Controller for :func:`shiny.ui.accordion`.""" loc: Locator """ @@ -4825,7 +4825,8 @@ def accordion_panel( data_value: str, ) -> AccordionPanel: """ - Returns the accordion panel with the specified data value. + Returns the accordion panel (:class:`~shiny.playwright.controls.AccordionPanel`) + with the specified data value. Parameters ---------- @@ -4840,7 +4841,7 @@ class AccordionPanel( _UiWithContainer, ): """ - AccordionPanel control for :func:`~shiny.ui.accordion_panel`. + Controller for :func:`shiny.ui.accordion_panel`. """ loc_label: Locator @@ -5119,7 +5120,7 @@ def expect_placement(self, value: str, *, timeout: Timeout = None) -> None: class Popover(_OverlayBase): - """Popover control for :func:`~shiny.ui.popover`.""" + """Controller for :func:`shiny.ui.popover`.""" loc_trigger: Locator """ @@ -5182,7 +5183,7 @@ def toggle(self, timeout: Timeout = None) -> None: class Tooltip(_OverlayBase): - """Tooltip control for :func:`~shiny.ui.tooltip`.""" + """Controller for :func:`shiny.ui.tooltip`.""" loc_container: Locator """ @@ -5374,7 +5375,7 @@ def expect_nav_titles( class NavItem(_UiWithContainer): - """Navigation item control for :func:`~shiny.ui.nav_item`.""" + """Controller for :func:`shiny.ui.nav_item`.""" """ Playwright `Locator` for the content of the nav item. @@ -5463,7 +5464,7 @@ def expect_content(self, value: PatternOrStr, *, timeout: Timeout = None) -> Non class NavsetTab(_NavItemBase): - """NavsetTab control for :func:`~shiny.ui.navset_tab`.""" + """Controller for :func:`shiny.ui.navset_tab`.""" loc: Locator """ @@ -5494,7 +5495,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetPill(_NavItemBase): - """NavsetPill control for :func:`~shiny.ui.navset_pill`.""" + """Controller for :func:`shiny.ui.navset_pill`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5516,7 +5517,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetUnderline(_NavItemBase): - """NavsetUnderline control for :func:`~shiny.ui.navset_underline`.""" + """Controller for :func:`shiny.ui.navset_underline`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5538,7 +5539,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetPillList(_NavItemBase): - """NavsetPillList control for :func:`~shiny.ui.navset_pill_list`.""" + """Controller for :func:`shiny.ui.navset_pill_list`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5560,7 +5561,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetCardTab(_NavItemBase): - """NavsetCardTab control for :func:`~shiny.ui.navset_card_tab`.""" + """Controller for :func:`shiny.ui.navset_card_tab`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5582,7 +5583,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetCardPill(_NavItemBase): - """NavsetCardPill control for :func:`~shiny.ui.navset_card_pill`.""" + """Controller for :func:`shiny.ui.navset_card_pill`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5604,7 +5605,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetCardUnderline(_NavItemBase): - """NavsetCardUnderline control for :func:`~shiny.ui.navset_card_underline`.""" + """Controller for :func:`shiny.ui.navset_card_underline`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5626,7 +5627,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetHidden(_NavItemBase): - """NavsetHidden control for :func:`~shiny.ui.navset_hidden`.""" + """Controller for :func:`shiny.ui.navset_hidden`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5648,7 +5649,7 @@ def __init__(self, page: Page, id: str) -> None: class NavsetBar(_NavItemBase): - """NavsetBar control for :func:`~shiny.ui.navset_bar`.""" + """Controller for :func:`shiny.ui.navset_bar`.""" def __init__(self, page: Page, id: str) -> None: """ @@ -5671,7 +5672,7 @@ def __init__(self, page: Page, id: str) -> None: class OutputDataFrame(_UiWithContainer): """ - OutputDataFrame control for :func:`~shiny.ui.output_data_frame`. + Controller for :func:`shiny.ui.output_data_frame`. """ loc_container: Locator @@ -6161,7 +6162,7 @@ def expect_cell_title( # TODO: Use mixin for dowloadlink and download button class DownloadLink(_InputActionBase): """ - DownloadLink control for :func:`~shiny.ui.download_link`. + Controller for :func:`shiny.ui.download_link`. """ def __init__(self, page: Page, id: str) -> None: @@ -6187,7 +6188,7 @@ class DownloadButton( _InputActionBase, ): """ - DownloadButton control for :func:`~shiny.ui.download_button` + Controller for :func:`shiny.ui.download_button` """ def __init__(self, page: Page, id: str) -> None: diff --git a/shiny/playwright/expect/_expect.py b/shiny/playwright/expect/_expect.py index 89734bc94..dd83a6899 100644 --- a/shiny/playwright/expect/_expect.py +++ b/shiny/playwright/expect/_expect.py @@ -8,6 +8,7 @@ from playwright.sync_api import Locator from playwright.sync_api import expect as playwright_expect +from ..._docstring import no_example from ..._typing_extensions import assert_type from .._types import AttrValue, PatternOrStr, PatternStr, StyleValue, Timeout @@ -21,6 +22,7 @@ ) +@no_example() def expect_attribute_to_have_value( loc: Locator, name: str, @@ -39,6 +41,7 @@ def expect_attribute_to_have_value( playwright_expect(loc).to_have_attribute(name=name, value=value, timeout=timeout) +@no_example() def expect_to_have_class( loc: Locator, cls: str, @@ -49,6 +52,7 @@ def expect_to_have_class( playwright_expect(loc).to_have_class(cls_regex, timeout=timeout) +@no_example() def expect_not_to_have_class( loc: Locator, cls: str, @@ -59,6 +63,7 @@ def expect_not_to_have_class( playwright_expect(loc).not_to_have_class(cls_regex, timeout=timeout) +@no_example() def expect_to_have_style( loc: Locator, css_key: str, diff --git a/shiny/playwright/expect/_expect_to_change.py b/shiny/playwright/expect/_expect_to_change.py index a5800095d..284b37d02 100644 --- a/shiny/playwright/expect/_expect_to_change.py +++ b/shiny/playwright/expect/_expect_to_change.py @@ -3,9 +3,12 @@ from contextlib import contextmanager from typing import Any, Callable, Generator +from ..._docstring import no_example + __all__ = ("expect_to_change",) +@no_example() @contextmanager def expect_to_change( func: Callable[[], Any], timeout_secs: float = 10 @@ -48,6 +51,7 @@ def wait_for_change(): wait_for_change() +@no_example() def retry_with_timeout(timeout: float = 30): """ Decorator that retries a function until 1) it succeeds, 2) fails with a diff --git a/shiny/pytest/_fixture.py b/shiny/pytest/_fixture.py index 9732fa53d..e0c3c6df2 100644 --- a/shiny/pytest/_fixture.py +++ b/shiny/pytest/_fixture.py @@ -5,6 +5,7 @@ import pytest +from .._docstring import no_example from ..run._run import shiny_app_gen __all__ = ( @@ -16,6 +17,7 @@ ScopeName = Literal["session", "package", "module", "class", "function"] +@no_example() def create_app_fixture( app: Union[PurePath, str], scope: ScopeName = "module", diff --git a/shiny/run/_run.py b/shiny/run/_run.py index c07c6a532..6323c79e3 100644 --- a/shiny/run/_run.py +++ b/shiny/run/_run.py @@ -9,6 +9,7 @@ from types import TracebackType from typing import IO, Any, Callable, Generator, List, Optional, TextIO, Type, Union +from .._docstring import no_example from .._utils import random_port __all__ = ( @@ -200,6 +201,7 @@ def stderr_uvicorn(line: str) -> bool: ) +@no_example() def run_shiny_app( app_file: Union[str, PurePath], *,