Skip to content

Commit 9ac4d9d

Browse files
committed
Improve relative app path handling in test generation
Refactored SYSTEM_PROMPT_testing.md to clarify rules for relative app paths in create_app_fixture. Updated main.py to compute and rewrite app fixture paths to be relative to the test file directory, ensuring generated tests use correct relative paths. Simplified workflow logic and dependency installation in verify-testing-docs-on-change.yml.
1 parent e973f82 commit 9ac4d9d

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

.github/workflows/verify-testing-docs-on-change.yml

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,20 @@ jobs:
2121
with:
2222
fetch-depth: 0
2323

24-
- name: Check for controller changes
25-
id: check-controller
26-
run: |
27-
# Check if any files in shiny/playwright/controller have changed
28-
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q '^shiny/playwright/controller/'; then
29-
echo "controller_changed=true" >> $GITHUB_OUTPUT
30-
echo "Changes detected in shiny/playwright/controller directory"
31-
else
32-
echo "controller_changed=false" >> $GITHUB_OUTPUT
33-
echo "No changes detected in shiny/playwright/controller directory"
34-
fi
35-
3624
- name: Set up Python
37-
if: steps.check-controller.outputs.controller_changed == 'true'
3825
uses: actions/setup-python@v5
3926
with:
4027
python-version: '3.13'
4128

4229
- name: Install uv
43-
if: steps.check-controller.outputs.controller_changed == 'true'
4430
uses: astral-sh/setup-uv@v4
4531

4632
- name: Install dependencies
47-
if: steps.check-controller.outputs.controller_changed == 'true'
4833
run: |
4934
uv pip install --system --upgrade pip
50-
uv pip install --system -e ".[dev,test]"
35+
uv pip install --system -e ".[dev,test,doc]"
5136
5237
- name: Update testing docs and check for changes
53-
if: steps.check-controller.outputs.controller_changed == 'true'
5438
id: check-docs-changes
5539
run: |
5640
# Store the current state of the documentation file
@@ -59,10 +43,14 @@ jobs:
5943
# Run the make command to update testing docs
6044
make update-testing-docs
6145
46+
if [[ ! -f documentation_testing_before.json || ! -f shiny/pytest/generate/data/docs/documentation_testing.json ]]; then
47+
echo "One or both documentation files are missing."
48+
exit 1
49+
fi
50+
6251
# Check if the documentation file has changed
6352
if ! diff -q documentation_testing_before.json shiny/pytest/generate/data/docs/documentation_testing.json > /dev/null 2>&1; then
6453
echo "docs_changed=true" >> $GITHUB_OUTPUT
65-
echo "Documentation file has changed after running make update-testing-docs"
6654
echo "The generated documentation is out of sync with the current controller changes."
6755
exit 1
6856
else
@@ -100,7 +88,7 @@ jobs:
10088
*This comment was automatically generated by the validate_testing_docs workflow.*
10189
10290
- name: Remove comment when no controller changes or docs are up to date
103-
if: steps.check-controller.outputs.controller_changed == 'false' || (steps.check-controller.outputs.controller_changed == 'true' && steps.check-docs-changes.outputs.docs_changed == 'false')
91+
if: steps.check-docs-changes.outputs.docs_changed == 'false'
10492
uses: marocchino/sticky-pull-request-comment@v2
10593
with:
10694
header: testing-docs-update

shiny/pytest/generate/data/prompts/SYSTEM_PROMPT_testing.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ For non-Shiny Python code, respond: "This framework is for Shiny for Python only
77

88
## Core Rules
99

10-
1. **Dynamic App File**: Use exact filename from prompt in `create_app_fixture(["filename.py"])`
10+
1. **Dynamic App File**: When generating code that uses `create_app_fixture`, follow these rules:
11+
- Use the exact filename provided in the prompt.
12+
- If the test file is under `app_dir/tests`, make the app path relative to the tests directory.
13+
14+
-`app = create_app_fixture(["../app.py"])`
15+
-`app = create_app_fixture(["app.py"])`
16+
17+
- If the provided filename is in a different path, adjust the path accordingly while keeping it relative.
1118

1219
2. **Controller Classes Only**: Always use official controllers, never `page.locator()`
1320
-`controller.InputSlider(page, "my_slider")`
@@ -33,11 +40,11 @@ For non-Shiny Python code, respond: "This framework is for Shiny for Python only
3340
3441
8. **Skip plots**: Do not test plot content or functionality i.e. using OutputPlot controller.
3542
36-
9. **Keyword-Only Args**: Always pass every argument as a keyword for every controller method.
43+
9. **Keyword-Only Args**: Always pass every argument as a keyword for every controller method.
3744
- ✅ `expect_cell(value="0", row=1, col=2)`
3845
- ❌ `expect_cell("0", 1, 2)`
3946
40-
10. **Newline at End**: Always end files with a newline.
47+
10. **Newline at End**: Always end files with a newline.
4148
4249
## Examples
4350

shiny/pytest/generate/main.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import importlib.resources
22
import logging
3+
import os
34
import re
45
import sys
56
from dataclasses import dataclass
@@ -219,6 +220,44 @@ def extract_test(self, response: str) -> str:
219220
match = self.CODE_PATTERN.search(response)
220221
return match.group(1).strip() if match else ""
221222

223+
def _compute_relative_app_path(
224+
self, app_file_path: Path, test_file_path: Path
225+
) -> str:
226+
"""Compute POSIX-style relative path from the test file directory to the app file."""
227+
rel = os.path.relpath(str(app_file_path), start=str(test_file_path.parent))
228+
return Path(rel).as_posix()
229+
230+
def _rewrite_fixture_path(self, test_code: str, relative_app_path: str) -> str:
231+
"""Rewrite create_app_fixture path to be relative to the test file directory.
232+
233+
Handles common patterns like:
234+
- create_app_fixture(["app.py"]) -> create_app_fixture(["../app.py"]) (or appropriate)
235+
- create_app_fixture("app.py") -> create_app_fixture("../app.py")
236+
Keeps other arguments intact if present.
237+
"""
238+
# Pattern for list form: create_app_fixture(["app.py"]) or with spaces
239+
pattern_list = re.compile(
240+
r"(create_app_fixture\(\s*\[\s*)(['\"])([^'\"]+)(\2)(\s*)([,\]])",
241+
re.DOTALL,
242+
)
243+
244+
def repl_list(m: re.Match) -> str:
245+
return f"{m.group(1)}{m.group(2)}{relative_app_path}{m.group(2)}{m.group(5)}{m.group(6)}"
246+
247+
new_code, _ = pattern_list.subn(repl_list, test_code)
248+
249+
# Pattern for direct string form: create_app_fixture("app.py")
250+
pattern_str = re.compile(
251+
r"(create_app_fixture\(\s*)(['\"])([^'\"]+)(\2)(\s*)([,\)])",
252+
re.DOTALL,
253+
)
254+
255+
def repl_str(m: re.Match) -> str:
256+
return f"{m.group(1)}{m.group(2)}{relative_app_path}{m.group(2)}{m.group(5)}{m.group(6)}"
257+
258+
new_code2, _ = pattern_str.subn(repl_str, new_code)
259+
return new_code2
260+
222261
def _create_test_prompt(self, app_text: str, app_file_name: str) -> str:
223262
"""Create test generation prompt with app file name"""
224263
return (
@@ -227,8 +266,10 @@ def _create_test_prompt(self, app_text: str, app_file_name: str) -> str:
227266
"Do not add tests for ones that do not have an existing ids since controllers need IDs to locate elements.\n"
228267
"and server functionality of this app. Include appropriate assertions \n"
229268
"and test cases to verify the app's behavior.\n"
230-
f"IMPORTANT: Use the exact app file name '{app_file_name}' in the create_app_fixture call like this:\n"
231-
f'app = create_app_fixture(["{app_file_name}"])\n'
269+
"IMPORTANT: In the create_app_fixture call, pass a RELATIVE path from the test file's directory to the app file.\n"
270+
"For example, if the test lives under a 'tests/' subfolder next to the app file, use '../"
271+
+ app_file_name
272+
+ "'. Do not use absolute paths.\n"
232273
"IMPORTANT: Only output the Python test code in a single code block. Do not include any explanation, justification, or extra text."
233274
)
234275

@@ -315,6 +356,14 @@ def generate_test(
315356
inferred_app_path, output_dir_path
316357
)
317358

359+
try:
360+
relative_app_path = self._compute_relative_app_path(
361+
inferred_app_path, test_file_path
362+
)
363+
test_code = self._rewrite_fixture_path(test_code, relative_app_path)
364+
except Exception:
365+
pass
366+
318367
test_file_path.parent.mkdir(parents=True, exist_ok=True)
319368
test_file_path.write_text(test_code, encoding="utf-8")
320369

0 commit comments

Comments
 (0)