Skip to content

Commit 2d7be3e

Browse files
committed
Add array support to create_app_fixture(app) to test multiple files
1 parent 29a8d3b commit 2d7be3e

File tree

2 files changed

+79
-12
lines changed

2 files changed

+79
-12
lines changed

shiny/pytest/_fixture.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,24 @@
3434

3535
@no_example()
3636
def create_app_fixture(
37-
app: Union[PurePath, str],
37+
app: PurePath | str | list[PurePath | str],
3838
scope: ScopeName = "module",
3939
):
4040
"""
4141
Create a fixture for a local Shiny app directory.
4242
43-
Creates a fixture for a local Shiny app that is not contained within the same folder. This fixture is used to start the Shiny app process and return the local URL of the app.
43+
Creates a fixture for a local Shiny app that is not contained within the same
44+
folder. This fixture is used to start the Shiny app process and return the local URL
45+
of the app.
4446
45-
If the app path is located in the same directory as the test file, then `create_app_fixture()` can be skipped and `local_app` test fixture can be used instead.
47+
If the app path is located in the same directory as the test file, then
48+
`create_app_fixture()` can be skipped and `local_app` test fixture can be used
49+
instead.
4650
4751
Parameters
4852
----------
4953
app
50-
The path to the Shiny app file.
54+
The path (or a list of paths) to the Shiny app file.
5155
5256
If `app` is a `Path` or `PurePath` instance and `Path(app).is_file()` returns
5357
`True`, then this value will be used directly. Note, `app`'s file path will be
@@ -58,8 +62,14 @@ def create_app_fixture(
5862
the test function was collected.
5963
6064
To be sure that your `app` path is always relative, supply a `str` value.
65+
66+
If `app` is a list of path values, then the fixture will be parametrized and each test
67+
will be run for each path in the list.
6168
scope
62-
The scope of the fixture.
69+
The scope of the fixture. The default is `module`, which means that the fixture
70+
will be created once per module. See [Pytest fixture
71+
scopes](https://docs.pytest.org/en/stable/how-to/fixtures.html#fixture-scopes)
72+
for more details.
6373
6474
Returns
6575
-------
@@ -85,13 +95,51 @@ def test_app_code(page: Page, app: ShinyAppProc):
8595
# Add test code here
8696
...
8797
```
98+
99+
```python
100+
from playwright.sync_api import Page
101+
102+
from shiny.playwright import controller
103+
from shiny.pytest import create_app_fixture
104+
from shiny.run import ShinyAppProc
105+
106+
# The variable name `app` MUST match the parameter name in the test function
107+
# The tests below will run for each path provided
108+
app = create_app_fixture(["relative/path/to/first/app.py", "relative/path/to/second/app.py"])
109+
110+
def test_app_code(page: Page, app: ShinyAppProc):
111+
112+
page.goto(app.url)
113+
# Add test code here
114+
...
115+
116+
def test_more_app_code(page: Page, app: ShinyAppProc):
117+
118+
page.goto(app.url)
119+
# Add test code here
120+
...
121+
```
88122
"""
89123

90-
@pytest.fixture(scope=scope)
91-
def fixture_func(request: pytest.FixtureRequest):
124+
def yield_app(app: PurePath | str):
92125
app_purepath_exists = isinstance(app, PurePath) and Path(app).is_file()
93-
app_path = app if app_purepath_exists else request.path.parent / app
126+
app_path = app if app_purepath_exists else Path(app)
94127
sa_gen = shiny_app_gen(app_path)
95128
yield next(sa_gen)
96129

130+
if isinstance(app, list):
131+
132+
# Multiple app values provided
133+
# Will display the app value as a parameter in the logs
134+
@pytest.fixture(scope=scope, params=app)
135+
def fixture_func(request: pytest.FixtureRequest):
136+
yield yield_app(request.param)
137+
138+
else:
139+
# Single app value provided
140+
# No indication of the app value in the logs
141+
@pytest.fixture(scope=scope)
142+
def fixture_func(request: pytest.FixtureRequest):
143+
yield yield_app(app)
144+
97145
return fixture_func

tests/playwright/shiny/tests_for_ai_generated_apps/accordion/test_accordion_core_express.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import pytest
21
from playwright.sync_api import Page
32

43
from shiny.playwright import controller
4+
from shiny.pytest._fixture import create_app_fixture
55
from shiny.run import ShinyAppProc
66

7+
app = create_app_fixture(["app-core.py", "app-express.py"])
78

8-
@pytest.mark.parametrize("local_app", ["app-core.py", "app-express.py"], indirect=True)
9-
def test_accordion_demo(page: Page, local_app: ShinyAppProc) -> None:
10-
page.goto(local_app.url)
9+
# For this file, separate the tests to "prove" that the fixture exists for the whole module
10+
11+
12+
def test_accordion_demo1(page: Page, app: ShinyAppProc) -> None:
13+
page.goto(app.url)
1114

1215
# Test accordion
1316
accordion = controller.Accordion(page, "acc_demo")
@@ -33,6 +36,22 @@ def test_accordion_demo(page: Page, local_app: ShinyAppProc) -> None:
3336
panel_c.expect_label("Panel C")
3437
panel_d.expect_label("Panel D")
3538

39+
40+
def test_accordion_demo2(page: Page, app: ShinyAppProc) -> None:
41+
page.goto(app.url)
42+
43+
# Test accordion
44+
accordion = controller.Accordion(page, "acc_demo")
45+
46+
# Test initial state - Panel B and D should be open by default
47+
accordion.expect_multiple(True)
48+
49+
# Test individual panels
50+
panel_a = accordion.accordion_panel("Panel A")
51+
panel_b = accordion.accordion_panel("Panel B")
52+
panel_c = accordion.accordion_panel("Panel C")
53+
_panel_d = accordion.accordion_panel("Panel D")
54+
3655
# Test panel content
3756
panel_a.expect_body("This is a basic accordion panel with default settings.")
3857
panel_b.expect_body("This panel has a custom star icon and is open by default.")

0 commit comments

Comments
 (0)