diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml
index a20a1a9a5..ad355f70b 100644
--- a/.github/workflows/pytest.yaml
+++ b/.github/workflows/pytest.yaml
@@ -187,7 +187,6 @@ jobs:
- name: Install node.js
uses: actions/setup-node@v4
with:
- node-version: "18"
cache: npm
cache-dependency-path: examples/brownian/shinymediapipe/package-lock.json
- name: Install node.js package
@@ -206,6 +205,49 @@ jobs:
path: test-results/
retention-days: 5
+ playwright-ai:
+ if: github.event_name != 'release'
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.12", "3.11", "3.10", "3.9"]
+ browser: ["chromium", "firefox", "webkit"]
+ exclude:
+ - python-version: ${{ github.event.pull_request.draft && '3.11' }}
+ - python-version: ${{ github.event.pull_request.draft && '3.10' }}
+ - python-version: ${{ github.event.pull_request.draft && '3.9' }}
+ - browser: ${{ github.event.pull_request.draft && 'firefox' }}
+ - browser: ${{ github.event.pull_request.draft && 'webkit' }}
+ fail-fast: false
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Setup py-shiny
+ uses: ./.github/py-shiny/setup
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Determine browsers for testing
+ uses: ./.github/py-shiny/pytest-browsers
+ id: browsers
+ with:
+ browser: ${{ matrix.browser }}
+ # If anything other than `true`, it will heavily reduce webkit performance
+ # Related: https://github.com/microsoft/playwright/issues/18119
+ disable-playwright-diagnostics: ${{ matrix.browser == 'webkit' || matrix.browser == 'firefox' }}
+
+ - name: Run playwright tests for AI generated apps
+ timeout-minutes: 60
+ run: |
+ make playwright-ai SUB_FILE=". --numprocesses 3 ${{ steps.browsers.outputs.playwright-diagnostic-args }}" ${{ steps.browsers.outputs.browsers }}
+ - uses: actions/upload-artifact@v4
+ if: failure() && steps.browsers.outputs.has-playwright-diagnostics
+ with:
+ name: "playright-ai-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.browser }}-results"
+ path: test-results/
+ retention-days: 5
+
playwright-deploys-precheck:
if: github.event_name != 'release'
runs-on: ubuntu-latest
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c2b84243..3d4b61bc4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Fixed an issue where the `.update_user_input()` method on `ui.Chat()` isn't working in shinylive. (#1891)
+* Fixed an issue with the `.click()` method on InputActionButton controllers in `shiny.playwright.controllers` where the method would not work as expected. (#1886)
+
## [1.3.0] - 2025-03-03
### New features
diff --git a/Makefile b/Makefile
index ca216e7dd..215ef4cb4 100644
--- a/Makefile
+++ b/Makefile
@@ -157,6 +157,7 @@ TEST_FILE:=tests/playwright/$(SUB_FILE)
DEPLOYS_TEST_FILE:=tests/playwright/deploys$(SUB_FILE)
SHINY_TEST_FILE:=tests/playwright/shiny/$(SUB_FILE)
EXAMPLES_TEST_FILE:=tests/playwright/examples/$(SUB_FILE)
+AI_TEST_FILE:=tests/playwright/ai_generated_apps/$(SUB_FILE)
install-playwright: FORCE
playwright install --with-deps
@@ -187,6 +188,10 @@ playwright-deploys: FORCE
playwright-examples: FORCE
$(MAKE) playwright TEST_FILE="$(EXAMPLES_TEST_FILE)"
+# end-to-end tests for all AI generated apps
+playwright-ai: FORCE
+ $(MAKE) playwright TEST_FILE="$(AI_TEST_FILE)"
+
coverage: FORCE ## check combined code coverage (must run e2e last)
pytest --cov-report term-missing --cov=shiny tests/pytest/ $(SHINY_TEST_FILE) $(PYTEST_BROWSERS)
coverage html
diff --git a/pyrightconfig.json b/pyrightconfig.json
index 23f58eacb..236aed7fc 100644
--- a/pyrightconfig.json
+++ b/pyrightconfig.json
@@ -10,7 +10,7 @@
"docs",
"tests/playwright/deploys/*/app.py",
"shiny/templates",
- "tests/playwright/shiny/tests_for_ai_generated_apps"
+ "tests/playwright/ai_generated_apps",
],
"typeCheckingMode": "strict",
"reportImportCycles": "none",
diff --git a/shiny/playwright/controller/_base.py b/shiny/playwright/controller/_base.py
index bd4433ab7..acde32d97 100644
--- a/shiny/playwright/controller/_base.py
+++ b/shiny/playwright/controller/_base.py
@@ -123,8 +123,12 @@ def set_text(
def _expect_multiple(loc: Locator, multiple: bool, timeout: Timeout = None) -> None:
- value = "True" if multiple else None
- _expect_style_to_have_value(loc, "multiple", value, timeout=timeout)
+ _expect_attribute_to_have_value(
+ loc,
+ "multiple",
+ value="multiple" if multiple else None,
+ timeout=timeout,
+ )
######################################################
diff --git a/shiny/playwright/controller/_input_buttons.py b/shiny/playwright/controller/_input_buttons.py
index 37433a80a..287053185 100644
--- a/shiny/playwright/controller/_input_buttons.py
+++ b/shiny/playwright/controller/_input_buttons.py
@@ -101,7 +101,7 @@ def click(self, *, timeout: Timeout = None):
timeout
The maximum time to wait for the input dark mode to be clicked. Defaults to `None`.
"""
- self.loc.click(timeout=timeout)
+ self.loc.locator("button").click(timeout=timeout)
return self
def expect_mode(self, value: str, *, timeout: Timeout = None):
diff --git a/tests/playwright/shiny/tests_for_ai_generated_apps/accordion/app-core.py b/tests/playwright/ai_generated_apps/accordion/app-core.py
similarity index 100%
rename from tests/playwright/shiny/tests_for_ai_generated_apps/accordion/app-core.py
rename to tests/playwright/ai_generated_apps/accordion/app-core.py
diff --git a/tests/playwright/shiny/tests_for_ai_generated_apps/accordion/app-express.py b/tests/playwright/ai_generated_apps/accordion/app-express.py
similarity index 100%
rename from tests/playwright/shiny/tests_for_ai_generated_apps/accordion/app-express.py
rename to tests/playwright/ai_generated_apps/accordion/app-express.py
diff --git a/tests/playwright/shiny/tests_for_ai_generated_apps/accordion/test_accordion_core_express.py b/tests/playwright/ai_generated_apps/accordion/test_accordion_core_express.py
similarity index 100%
rename from tests/playwright/shiny/tests_for_ai_generated_apps/accordion/test_accordion_core_express.py
rename to tests/playwright/ai_generated_apps/accordion/test_accordion_core_express.py
diff --git a/tests/playwright/ai_generated_apps/accordion_panel/app-core.py b/tests/playwright/ai_generated_apps/accordion_panel/app-core.py
new file mode 100644
index 000000000..1ef556042
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/accordion_panel/app-core.py
@@ -0,0 +1,65 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Add Font Awesome CSS in the head section
+ ui.tags.head(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css",
+ )
+ ),
+ # Card containing accordion
+ ui.card(
+ ui.accordion(
+ # Basic Panel
+ ui.accordion_panel(
+ "Basic Panel",
+ ui.markdown("This is a basic panel with just a title parameter"),
+ ),
+ # Panel with title and value
+ ui.accordion_panel(
+ "Panel with Value",
+ ui.markdown("This panel has both a title and a value parameter"),
+ value="panel2",
+ ),
+ # Panel with title, value, and icon
+ ui.accordion_panel(
+ "Panel with Icon",
+ ui.markdown("This panel includes an icon parameter using Font Awesome"),
+ value="panel3",
+ icon=ui.tags.i(
+ class_="fa-solid fa-shield-halved", style="font-size: 1rem;"
+ ),
+ ),
+ # Panel with title, value, icon, and custom attributes
+ ui.accordion_panel(
+ "Panel with Custom Attributes",
+ ui.markdown(
+ "This panel demonstrates custom attributes (class and style)"
+ ),
+ value="panel4",
+ icon=ui.tags.i(class_="fa-solid fa-star", style="font-size: 1rem;"),
+ class_="custom-panel",
+ style="background-color: #f8f9fa;",
+ ),
+ id="acc",
+ open=True,
+ multiple=True,
+ ),
+ ),
+ # Output for selected panel
+ ui.output_text("selected_panel"),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @output
+ @render.text
+ def selected_panel():
+ return f"Currently selected panel: {input.acc()}"
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/accordion_panel/app-express.py b/tests/playwright/ai_generated_apps/accordion_panel/app-express.py
new file mode 100644
index 000000000..e4f72674d
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/accordion_panel/app-express.py
@@ -0,0 +1,46 @@
+from shiny.express import input, render, ui
+
+# Add Font Awesome CSS in the head section first
+ui.head_content(
+ ui.HTML(
+ ''
+ )
+)
+
+ui.page_opts(fillable=True)
+
+with ui.card():
+ with ui.accordion(id="acc", open=True, multiple=True):
+ # Panel with just title
+ with ui.accordion_panel("Basic Panel"):
+ ui.markdown("This is a basic panel with just a title parameter")
+
+ # Panel with title and value
+ with ui.accordion_panel("Panel with Value", value="panel2"):
+ ui.markdown("This panel has both a title and a value parameter")
+
+ # Panel with title, value, and icon
+ with ui.accordion_panel(
+ "Panel with Icon",
+ value="panel3",
+ icon=ui.tags.i(
+ class_="fa-solid fa-shield-halved", style="font-size: 1rem;"
+ ),
+ ):
+ ui.markdown("This panel includes an icon parameter using Font Awesome")
+
+ # Panel with title, value, icon, and custom attributes
+ with ui.accordion_panel(
+ "Panel with Custom Attributes",
+ value="panel4",
+ icon=ui.tags.i(class_="fa-solid fa-star", style="font-size: 1rem;"),
+ class_="custom-panel",
+ style="background-color: #f8f9fa;",
+ ):
+ ui.markdown("This panel demonstrates custom attributes (class and style)")
+
+
+# Show which panel is currently selected
+@render.text
+def selected_panel():
+ return f"Currently selected panel: {input.acc()}"
diff --git a/tests/playwright/ai_generated_apps/accordion_panel/test_accordion_panel_core_express.py b/tests/playwright/ai_generated_apps/accordion_panel/test_accordion_panel_core_express.py
new file mode 100644
index 000000000..30d34eba9
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/accordion_panel/test_accordion_panel_core_express.py
@@ -0,0 +1,59 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_accordion_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test accordion with ID "acc"
+ accordion = controller.Accordion(page, "acc")
+
+ # Test that multiple panels can be open
+ accordion.expect_multiple(True)
+
+ # Test that accordion is initially open
+ panel1 = accordion.accordion_panel("Basic Panel")
+ panel2 = accordion.accordion_panel("panel2")
+ panel3 = accordion.accordion_panel("panel3")
+ panel4 = accordion.accordion_panel("panel4")
+
+ # Test initial state
+ panel1.expect_open(True)
+ panel2.expect_open(True)
+ panel3.expect_open(True)
+ panel4.expect_open(True)
+
+ # Test panel labels
+ panel1.expect_label("Basic Panel")
+ panel2.expect_label("Panel with Value")
+ panel3.expect_label("Panel with Icon")
+ panel4.expect_label("Panel with Custom Attributes")
+
+ # Test panel content
+ panel1.expect_body("This is a basic panel with just a title parameter")
+ panel2.expect_body("This panel has both a title and a value parameter")
+ panel3.expect_body("This panel includes an icon parameter using Font Awesome")
+ panel4.expect_body("This panel demonstrates custom attributes (class and style)")
+
+ # Test icons (presence/absence)
+ panel1.expect_icon(False) # First panel has no icon
+ panel2.expect_icon(False) # Second panel has no icon
+ panel3.expect_icon(True) # Third panel has an icon
+ panel4.expect_icon(True) # Fourth panel has an icon
+
+ # Test closing and opening panels
+ panel1.set(False)
+ panel1.expect_open(False)
+ panel1.set(True)
+ panel1.expect_open(True)
+
+ # Test output text that shows selected panel
+ selected_text = controller.OutputText(page, "selected_panel")
+ selected_text.expect_value(
+ "Currently selected panel: ('Basic Panel', 'panel2', 'panel3', 'panel4')"
+ )
diff --git a/tests/playwright/ai_generated_apps/card/app-core.py b/tests/playwright/ai_generated_apps/card/app-core.py
new file mode 100644
index 000000000..c84a329fd
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/card/app-core.py
@@ -0,0 +1,46 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Card with all possible parameters
+ ui.card(
+ ui.card_header("Card Demo", "This demonstrates all card parameters"),
+ ui.markdown(
+ """
+ This is the main content of the card.
+ The card has various parameters set including:
+
+ * full_screen=True - allows expanding to full screen
+ * height='300px' - sets fixed height
+ * fill=True - allows card to grow/shrink
+ * class_='my-4' - adds custom CSS classes
+ """
+ ),
+ ui.card_footer("Card Footer", class_="text-muted"),
+ id="demo_card",
+ full_screen=True, # Allow card to be expanded to full screen
+ height="300px", # Set card height
+ fill=True, # Allow card to grow/shrink to fit container
+ class_="my-4", # Add custom CSS classes
+ ),
+ # Another card showing dynamic content
+ ui.card(
+ ui.card_header("Dynamic Content Demo"),
+ ui.output_text("dynamic_content"),
+ id="dynamic_card",
+ full_screen=True,
+ height="200px",
+ class_="mt-4",
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @render.text
+ def dynamic_content():
+ return "This card shows how to include dynamic content using render functions"
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/card/app-express.py b/tests/playwright/ai_generated_apps/card/app-express.py
new file mode 100644
index 000000000..6a30cdd9a
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/card/app-express.py
@@ -0,0 +1,39 @@
+from shiny.express import render, ui
+
+# Set page options
+ui.page_opts(fillable=True)
+
+# Card with all possible parameters
+with ui.card(
+ id="demo_card",
+ full_screen=True, # Allow card to be expanded to full screen
+ height="300px", # Set card height
+ fill=True, # Allow card to grow/shrink to fit container
+ class_="my-4", # Add custom CSS classes
+):
+ # Card header
+ ui.card_header("Card Demo", "This demonstrates all card parameters")
+
+ # Card body content
+ ui.markdown(
+ """
+ This is the main content of the card.
+ The card has various parameters set including:
+
+ * full_screen=True - allows expanding to full screen
+ * height='300px' - sets fixed height
+ * fill=True - allows card to grow/shrink
+ * class_='my-4' - adds custom CSS classes
+ """
+ )
+
+ # Card footer
+ ui.card_footer("Card Footer", class_="text-muted")
+
+# Another card showing dynamic content
+with ui.card(id="dynamic_card", full_screen=True, height="200px", class_="mt-4"):
+ ui.card_header("Dynamic Content Demo")
+
+ @render.text
+ def dynamic_content():
+ return "This card shows how to include dynamic content using render functions"
diff --git a/tests/playwright/ai_generated_apps/card/test_card_core_express.py b/tests/playwright/ai_generated_apps/card/test_card_core_express.py
new file mode 100644
index 000000000..7dad28078
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/card/test_card_core_express.py
@@ -0,0 +1,29 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_cards(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+ # Test demo_card
+ demo_card = controller.Card(page, "demo_card")
+ demo_card.expect_header("Card DemoThis demonstrates all card parameters")
+ demo_card.expect_height("300px")
+ demo_card.expect_footer("Card Footer")
+ demo_card.expect_full_screen_available(True)
+
+ # Test dynamic_card
+ dynamic_card = controller.Card(page, "dynamic_card")
+ dynamic_card.expect_header("Dynamic Content Demo")
+ dynamic_card.expect_height("200px")
+ dynamic_card.expect_full_screen_available(True)
+
+ # Test dynamic content in dynamic_card
+ dynamic_content = controller.OutputText(page, "dynamic_content")
+ dynamic_content.expect_value(
+ "This card shows how to include dynamic content using render functions"
+ )
diff --git a/tests/playwright/ai_generated_apps/card_footer/app-core.py b/tests/playwright/ai_generated_apps/card_footer/app-core.py
new file mode 100644
index 000000000..572ac84cd
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/card_footer/app-core.py
@@ -0,0 +1,52 @@
+from shiny import App, render, ui
+
+app_ui = ui.page_fillable(
+ # First card with basic footer
+ ui.card(
+ ui.card_header("Basic Card with Header"),
+ "This is a basic card with header and footer",
+ ui.card_footer("Footer content", class_="bg-light"),
+ full_screen=True,
+ height="300px",
+ id="card1",
+ ),
+ # Second card with complex footer
+ ui.card(
+ ui.card_header("Card with Complex Footer"),
+ "This card shows different types of content in footer",
+ ui.card_footer(
+ ui.HTML("Bold text"),
+ ui.tags.span(" | ", style="margin: 0 10px;"),
+ ui.tags.em("Emphasized text"),
+ ui.tags.span(" | ", style="margin: 0 10px;"),
+ ui.tags.span("Regular text", style="color: green;"),
+ ),
+ full_screen=True,
+ height="600px",
+ id="card2",
+ ),
+ # Third card with interactive footer
+ ui.card(
+ ui.card_header("Card with Interactive Footer"),
+ "This card has interactive elements in its footer",
+ ui.card_footer(
+ ui.input_action_button("btn", "Click Me", class_="btn-primary"),
+ ui.output_text("click_count"),
+ class_="d-flex justify-content-between align-items-center",
+ ),
+ full_screen=True,
+ height="400px",
+ id="card3",
+ ),
+)
+
+
+def server(input, output, session):
+ @render.text
+ def click_count():
+ if not input.btn():
+ return "No clicks yet"
+ return f"Clicked {input.btn()} times"
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/card_footer/app-express.py b/tests/playwright/ai_generated_apps/card_footer/app-express.py
new file mode 100644
index 000000000..22761bafc
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/card_footer/app-express.py
@@ -0,0 +1,36 @@
+from shiny.express import input, render, ui
+
+# Set page options including title
+ui.page_opts(title="Card Footer Demo", fillable=True)
+
+# Create a card with footer to demonstrate basic parameters
+with ui.card(full_screen=True, height="300px", id="card1"):
+ ui.card_header("Basic Card with Header")
+ "This is a basic card with header and footer"
+ ui.card_footer("Footer content", class_="bg-light")
+
+# Create another card to show different content types in footer
+with ui.card(full_screen=True, height="600px", id="card2"):
+ ui.card_header("Card with Complex Footer")
+ "This card shows different types of content in footer"
+
+ with ui.card_footer():
+ ui.HTML("Bold text")
+ ui.tags.span(" | ", style="margin: 0 10px;")
+ ui.tags.em("Emphasized text")
+ ui.tags.span(" | ", style="margin: 0 10px;")
+ ui.tags.span("Regular text", style="color: green;")
+
+# Add a third card with interactive elements in footer
+with ui.card(full_screen=True, height="400px", id="card3"):
+ ui.card_header("Card with Interactive Footer")
+ "This card has interactive elements in its footer"
+
+ with ui.card_footer(class_="d-flex justify-content-between align-items-center"):
+ ui.input_action_button("btn", "Click Me", class_="btn-primary")
+
+ @render.text
+ def click_count():
+ if not input.btn():
+ return "No clicks yet"
+ return f"Clicked {input.btn()} times"
diff --git a/tests/playwright/ai_generated_apps/card_footer/test_card_footer_core_express.py b/tests/playwright/ai_generated_apps/card_footer/test_card_footer_core_express.py
new file mode 100644
index 000000000..211000fa3
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/card_footer/test_card_footer_core_express.py
@@ -0,0 +1,47 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_cards_with_footer(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test card1
+ card1 = controller.Card(page, "card1")
+ card1.expect_header("Basic Card with Header")
+ card1.expect_height("300px")
+ card1.expect_footer("Footer content")
+ card1.expect_full_screen_available(True)
+
+ # Test card2
+ card2 = controller.Card(page, "card2")
+ card2.expect_header("Card with Complex Footer")
+ card2.expect_height("600px")
+ card2.expect_footer("Bold text | Emphasized text | Regular text")
+ card2.expect_full_screen_available(True)
+
+ # Test card3
+ card3 = controller.Card(page, "card3")
+ card3.expect_header("Card with Interactive Footer")
+ card3.expect_height("400px")
+ card3.expect_full_screen_available(True)
+
+ # Test the button and its interaction
+ btn = controller.InputActionButton(page, "btn")
+ btn.expect_label("Click Me")
+
+ # Test initial state
+ output_text = controller.OutputText(page, "click_count")
+ output_text.expect_value("No clicks yet")
+
+ # Test after clicking
+ btn.click()
+ output_text.expect_value("Clicked 1 times")
+
+ # Test multiple clicks
+ btn.click()
+ output_text.expect_value("Clicked 2 times")
diff --git a/tests/playwright/ai_generated_apps/input_action_button/app-core.py b/tests/playwright/ai_generated_apps/input_action_button/app-core.py
new file mode 100644
index 000000000..84e26dec3
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_action_button/app-core.py
@@ -0,0 +1,58 @@
+from shiny import App, render, ui
+
+# Create the UI
+app_ui = ui.page_fluid(
+ # Add Font Awesome CSS in the head
+ ui.tags.head(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css",
+ )
+ ),
+ # Main layout
+ ui.layout_column_wrap(
+ ui.card(
+ ui.card_header("Action Button Examples"),
+ # Basic button with width parameter
+ ui.input_action_button(id="btn1", label="Basic Button", width="200px"),
+ ui.br(), # Add spacing
+ # Button with icon and disabled state
+ ui.input_action_button(
+ id="btn2",
+ label="Disabled Button with Icon",
+ icon=ui.tags.i(class_="fa-solid fa-shield-halved"),
+ disabled=True,
+ ),
+ ui.br(), # Add spacing
+ # Button with custom class and style attributes
+ ui.input_action_button(
+ id="btn3",
+ label="Styled Button",
+ class_="btn-success",
+ style="margin-top: 20px;",
+ ),
+ ),
+ # Card for displaying results
+ ui.card(
+ ui.card_header("Click Counts"),
+ ui.output_text("click_counts"),
+ ),
+ width="100%",
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @output
+ @render.text
+ def click_counts():
+ return (
+ f"Button 1 clicks: {input.btn1() or 0}\n"
+ f"Button 2 clicks: {input.btn2() or 0}\n"
+ f"Button 3 clicks: {input.btn3() or 0}"
+ )
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_action_button/app-express.py b/tests/playwright/ai_generated_apps/input_action_button/app-express.py
new file mode 100644
index 000000000..d65be4267
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_action_button/app-express.py
@@ -0,0 +1,48 @@
+from shiny.express import input, render, ui
+
+# Add Font Awesome CSS for icons - this needs to be before any UI elements
+ui.head_content(
+ ui.HTML(
+ ''
+ )
+)
+
+# Create a layout with some spacing
+with ui.layout_column_wrap(width="100%"):
+ with ui.card():
+ ui.card_header("Action Button Examples")
+
+ # Basic button with width parameter
+ ui.input_action_button(id="btn1", label="Basic Button", width="200px")
+
+ ui.br() # Add some spacing
+
+ # Button with icon and disabled state
+ ui.input_action_button(
+ id="btn2",
+ label="Disabled Button with Icon",
+ icon=ui.tags.i(class_="fa-solid fa-shield-halved"),
+ disabled=True,
+ )
+
+ ui.br() # Add some spacing
+
+ # Button with custom class and style attributes
+ ui.input_action_button(
+ id="btn3",
+ label="Styled Button",
+ class_="btn-success",
+ style="margin-top: 20px;",
+ )
+
+ # Create another card for displaying results
+ with ui.card():
+ ui.card_header("Click Counts")
+
+ @render.text
+ def click_counts():
+ return (
+ f"Button 1 clicks: {input.btn1() or 0}\n"
+ f"Button 2 clicks: {input.btn2() or 0}\n"
+ f"Button 3 clicks: {input.btn3() or 0}"
+ )
diff --git a/tests/playwright/ai_generated_apps/input_action_button/test_input_action_button_core_express.py b/tests/playwright/ai_generated_apps/input_action_button/test_input_action_button_core_express.py
new file mode 100644
index 000000000..74e9c4027
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_action_button/test_input_action_button_core_express.py
@@ -0,0 +1,44 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_action_buttons(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test basic button (btn1)
+ btn1 = controller.InputActionButton(page, "btn1")
+ btn1.expect_label("Basic Button")
+ btn1.expect_width("200px")
+
+ # Test button with icon (btn2)
+ btn2 = controller.InputActionButton(page, "btn2")
+ btn2.expect_label("Disabled Button with Icon")
+
+ # Test styled button (btn3)
+ btn3 = controller.InputActionButton(page, "btn3")
+ btn3.expect_label("Styled Button")
+
+ # Test initial click counts
+ click_counts = controller.OutputText(page, "click_counts")
+ click_counts.expect_value(
+ "Button 1 clicks: 0\nButton 2 clicks: 0\nButton 3 clicks: 0"
+ )
+
+ # Test clicking btn1 and verify count updates
+ btn1.click()
+ click_counts.expect_value(
+ "Button 1 clicks: 1\nButton 2 clicks: 0\nButton 3 clicks: 0"
+ )
+
+ # Test clicking btn3 and verify count updates
+ btn3.click()
+ click_counts.expect_value(
+ "Button 1 clicks: 1\nButton 2 clicks: 0\nButton 3 clicks: 1"
+ )
+
+ # btn2 is disabled, so we don't test clicking it
diff --git a/tests/playwright/ai_generated_apps/input_action_link/app-core.py b/tests/playwright/ai_generated_apps/input_action_link/app-core.py
new file mode 100644
index 000000000..aaa54a556
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_action_link/app-core.py
@@ -0,0 +1,62 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Add Font Awesome CSS in the head section
+ ui.tags.head(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css",
+ )
+ ),
+ # Create layout with columns
+ ui.layout_column_wrap(
+ # First card
+ ui.card(
+ ui.card_header("Action Link Demo"),
+ # Create an action link with an icon
+ ui.input_action_link(
+ id="demo_link",
+ label="Click Me!",
+ icon=ui.tags.i(class_="fa-solid fa-shield-halved"),
+ ),
+ ui.output_text("link_clicks"),
+ full_screen=True,
+ height="300px",
+ id="card1",
+ ),
+ # Second card
+ ui.card(
+ ui.card_header("Click History"),
+ ui.output_text("click_history"),
+ full_screen=True,
+ height="300px",
+ id="card2",
+ ),
+ width=1 / 2,
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @output
+ @render.text
+ def link_clicks():
+ count = input.demo_link() or 0
+ return f"The link has been clicked {count} times"
+
+ @output
+ @render.text
+ def click_history():
+ count = input.demo_link() or 0
+ if count == 0:
+ return "No clicks yet!"
+ elif count == 1:
+ return "First click recorded!"
+ else:
+ return f"You've clicked {count} times. Keep going!"
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_action_link/app-express.py b/tests/playwright/ai_generated_apps/input_action_link/app-express.py
new file mode 100644
index 000000000..a3bb67e83
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_action_link/app-express.py
@@ -0,0 +1,43 @@
+from shiny.express import input, render, ui
+
+# Add Font Awesome CSS in the head section
+ui.head_content(
+ ui.HTML(
+ ''
+ )
+)
+
+# Set page title
+ui.page_opts(fillable=True)
+
+with ui.layout_column_wrap(width=1 / 2):
+ # Create a card to hold the action link
+ with ui.card(full_screen=True, height="300px", id="card1"):
+ ui.card_header("Action Link Demo")
+
+ # Create an action link with an icon
+ ui.input_action_link(
+ id="demo_link",
+ label="Click Me!",
+ icon=ui.tags.i(class_="fa-solid fa-shield-halved"),
+ )
+
+ # Display the click count
+ @render.text
+ def link_clicks():
+ count = input.demo_link() or 0
+ return f"The link has been clicked {count} times"
+
+ # Create another card to show reactive behavior
+ with ui.card(full_screen=True, height="300px", id="card2"):
+ ui.card_header("Click History")
+
+ @render.text
+ def click_history():
+ count = input.demo_link() or 0
+ if count == 0:
+ return "No clicks yet!"
+ elif count == 1:
+ return "First click recorded!"
+ else:
+ return f"You've clicked {count} times. Keep going!"
diff --git a/tests/playwright/ai_generated_apps/input_action_link/test_input_action_link_core_express.py b/tests/playwright/ai_generated_apps/input_action_link/test_input_action_link_core_express.py
new file mode 100644
index 000000000..bba4557b8
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_action_link/test_input_action_link_core_express.py
@@ -0,0 +1,44 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_action_link_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test first card
+ card1 = controller.Card(page, "card1")
+ card1.expect_header("Action Link Demo")
+ card1.expect_height("300px")
+ card1.expect_full_screen_available(True)
+
+ # Test second card
+ card2 = controller.Card(page, "card2")
+ card2.expect_header("Click History")
+ card2.expect_height("300px")
+ card2.expect_full_screen_available(True)
+
+ # Test action link
+ demo_link = controller.InputActionLink(page, "demo_link")
+ demo_link.expect_label("Click Me!")
+
+ # Test initial states of outputs
+ output_text = controller.OutputText(page, "link_clicks")
+ output_text.expect_value("The link has been clicked 0 times")
+
+ history_text = controller.OutputText(page, "click_history")
+ history_text.expect_value("No clicks yet!")
+
+ # Test interaction - first click
+ demo_link.click()
+ output_text.expect_value("The link has been clicked 1 times")
+ history_text.expect_value("First click recorded!")
+
+ # Test interaction - second click
+ demo_link.click()
+ output_text.expect_value("The link has been clicked 2 times")
+ history_text.expect_value("You've clicked 2 times. Keep going!")
diff --git a/tests/playwright/ai_generated_apps/input_checkbox/app-core.py b/tests/playwright/ai_generated_apps/input_checkbox/app-core.py
new file mode 100644
index 000000000..e75e6e118
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_checkbox/app-core.py
@@ -0,0 +1,40 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fluid(
+ # Card containing all elements
+ ui.card(
+ ui.card_header("Checkbox Demo"),
+ # Basic checkbox with default value (False)
+ ui.input_checkbox(id="basic", label="Basic checkbox"),
+ # Checkbox with initial value set to True
+ ui.input_checkbox(
+ id="preset_value", label="Checkbox with preset value", value=True
+ ),
+ # Checkbox with custom width
+ ui.input_checkbox(
+ id="custom_width",
+ label="Checkbox with custom width (300px)",
+ value=False,
+ width="300px",
+ ),
+ # Output UI for checkbox values
+ ui.output_ui("checkbox_values"),
+ )
+)
+
+
+# Define the server
+def server(input, output, session):
+ @output
+ @render.ui
+ def checkbox_values():
+ return ui.tags.div(
+ ui.tags.p(f"Basic checkbox value: {input.basic()}"),
+ ui.tags.p(f"Preset value checkbox: {input.preset_value()}"),
+ ui.tags.p(f"Custom width checkbox: {input.custom_width()}"),
+ )
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_checkbox/app-express.py b/tests/playwright/ai_generated_apps/input_checkbox/app-express.py
new file mode 100644
index 000000000..7e5a0bc61
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_checkbox/app-express.py
@@ -0,0 +1,27 @@
+from shiny.express import input, render, ui
+
+with ui.card():
+ ui.card_header("Checkbox Demo")
+
+ # Basic checkbox with default value (False)
+ ui.input_checkbox(id="basic", label="Basic checkbox")
+
+ # Checkbox with initial value set to True
+ ui.input_checkbox(id="preset_value", label="Checkbox with preset value", value=True)
+
+ # Checkbox with custom width
+ ui.input_checkbox(
+ id="custom_width",
+ label="Checkbox with custom width (300px)",
+ value=False,
+ width="300px",
+ )
+
+ # Display the values of all checkboxes
+ @render.ui
+ def checkbox_values():
+ return ui.tags.div(
+ ui.tags.p(f"Basic checkbox value: {input.basic()}"),
+ ui.tags.p(f"Preset value checkbox: {input.preset_value()}"),
+ ui.tags.p(f"Custom width checkbox: {input.custom_width()}"),
+ )
diff --git a/tests/playwright/ai_generated_apps/input_checkbox/test_input_checkbox_core_express.py b/tests/playwright/ai_generated_apps/input_checkbox/test_input_checkbox_core_express.py
new file mode 100644
index 000000000..18b68ce4f
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_checkbox/test_input_checkbox_core_express.py
@@ -0,0 +1,37 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_checkbox_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test basic checkbox
+ basic_checkbox = controller.InputCheckbox(page, "basic")
+ basic_checkbox.expect_label("Basic checkbox")
+ basic_checkbox.expect_checked(False) # Default value should be False
+
+ # Test checkbox with preset value
+ preset_checkbox = controller.InputCheckbox(page, "preset_value")
+ preset_checkbox.expect_label("Checkbox with preset value")
+ preset_checkbox.expect_checked(True) # Should be initially True
+
+ # Test checkbox with custom width
+ custom_width_checkbox = controller.InputCheckbox(page, "custom_width")
+ custom_width_checkbox.expect_label("Checkbox with custom width (300px)")
+ custom_width_checkbox.expect_checked(False) # Should be initially False
+ custom_width_checkbox.expect_width("300px")
+
+ # Test interactivity by changing values
+ basic_checkbox.set(True)
+ basic_checkbox.expect_checked(True)
+
+ preset_checkbox.set(False)
+ preset_checkbox.expect_checked(False)
+
+ custom_width_checkbox.set(True)
+ custom_width_checkbox.expect_checked(True)
diff --git a/tests/playwright/ai_generated_apps/input_checkbox_group/app-core.py b/tests/playwright/ai_generated_apps/input_checkbox_group/app-core.py
new file mode 100644
index 000000000..298f65316
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_checkbox_group/app-core.py
@@ -0,0 +1,39 @@
+from shiny import App, render, ui
+
+# Create sample choices with HTML formatting for demonstration
+choices = {
+ "red": ui.span("Red", style="color: #FF0000;"),
+ "green": ui.span("Green", style="color: #00AA00;"),
+ "blue": ui.span("Blue", style="color: #0000AA;"),
+}
+
+app_ui = ui.page_fluid(
+ ui.card(
+ ui.card_header("Color Selection Demo"),
+ # Using input_checkbox_group with all its parameters
+ ui.input_checkbox_group(
+ id="colors", # Required: unique identifier
+ label="Choose colors", # Required: label text
+ choices=choices, # Required: choices as dict with HTML formatting
+ selected=["red", "blue"], # Optional: pre-selected values
+ inline=True, # Optional: display choices inline
+ width="300px", # Optional: CSS width
+ ),
+ # Add some spacing
+ ui.hr(),
+ # Output for selected values
+ ui.output_text("selected_colors"),
+ )
+)
+
+
+def server(input, output, session):
+ @output
+ @render.text
+ def selected_colors():
+ if input.colors():
+ return f"You selected: {', '.join(input.colors())}"
+ return "No colors selected"
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_checkbox_group/app-express.py b/tests/playwright/ai_generated_apps/input_checkbox_group/app-express.py
new file mode 100644
index 000000000..a51840354
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_checkbox_group/app-express.py
@@ -0,0 +1,31 @@
+from shiny.express import input, render, ui
+
+# Create sample choices with HTML formatting for demonstration
+choices = {
+ "red": ui.span("Red", style="color: #FF0000;"),
+ "green": ui.span("Green", style="color: #00AA00;"),
+ "blue": ui.span("Blue", style="color: #0000AA;"),
+}
+
+with ui.card():
+ ui.card_header("Color Selection Demo")
+
+ # Using input_checkbox_group with all its parameters
+ ui.input_checkbox_group(
+ id="colors", # Required: unique identifier
+ label="Choose colors", # Required: label text
+ choices=choices, # Required: choices as dict with HTML formatting
+ selected=["red", "blue"], # Optional: pre-selected values
+ inline=True, # Optional: display choices inline
+ width="300px", # Optional: CSS width
+ )
+
+ # Add some spacing
+ ui.hr()
+
+ # Simple output to show selected values
+ @render.text
+ def selected_colors():
+ if input.colors():
+ return f"You selected: {', '.join(input.colors())}"
+ return "No colors selected"
diff --git a/tests/playwright/ai_generated_apps/input_checkbox_group/test_input_checkbox_group_core_express.py b/tests/playwright/ai_generated_apps/input_checkbox_group/test_input_checkbox_group_core_express.py
new file mode 100644
index 000000000..afcf9403b
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_checkbox_group/test_input_checkbox_group_core_express.py
@@ -0,0 +1,35 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_checkbox_group_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Get the checkbox group controller
+ colors = controller.InputCheckboxGroup(page, "colors")
+ selected_output = controller.OutputText(page, "selected_colors")
+
+ # Test initial state
+ colors.expect_label("Choose colors")
+ colors.expect_choices(["red", "green", "blue"])
+ colors.expect_selected(["red", "blue"]) # Check pre-selected values
+ colors.expect_inline(True)
+ colors.expect_width("300px")
+ selected_output.expect_value("You selected: red, blue")
+
+ # Test selecting different combinations
+ colors.set(["green"])
+ selected_output.expect_value("You selected: green")
+
+ # Test selecting multiple values
+ colors.set(["red", "green", "blue"])
+ selected_output.expect_value("You selected: red, green, blue")
+
+ # Test deselecting all values
+ colors.set([])
+ selected_output.expect_value("No colors selected")
diff --git a/tests/playwright/ai_generated_apps/input_dark_mode/app-core.py b/tests/playwright/ai_generated_apps/input_dark_mode/app-core.py
new file mode 100644
index 000000000..8cb4a95b4
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_dark_mode/app-core.py
@@ -0,0 +1,41 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Add Font Awesome CSS in head
+ ui.tags.head(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css",
+ )
+ ),
+ # Create layout with sidebar
+ ui.layout_sidebar(
+ # Sidebar contents
+ ui.sidebar(
+ ui.input_dark_mode(id="mode", mode="light"),
+ ),
+ # Main panel contents
+ ui.card(
+ ui.card_header("Dark Mode Example"),
+ ui.output_text("current_mode"),
+ ui.p("This is a demonstration of dark mode functionality."),
+ ui.div(
+ ui.tags.i(class_="fa-solid fa-moon", style="font-size: 2rem;"),
+ ui.span(" Dark Mode Toggle Example", class_="ms-2"),
+ class_="mt-3",
+ ),
+ ),
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @render.text
+ def current_mode():
+ return f"Current mode: {input.mode()}"
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_dark_mode/app-express.py b/tests/playwright/ai_generated_apps/input_dark_mode/app-express.py
new file mode 100644
index 000000000..f09e22482
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_dark_mode/app-express.py
@@ -0,0 +1,28 @@
+from shiny.express import input, render, ui
+
+# Add Font Awesome CSS
+ui.head_content(
+ ui.HTML(
+ ''
+ )
+)
+
+# Set page options
+ui.page_opts(fillable=True)
+
+with ui.layout_sidebar():
+ with ui.sidebar():
+ ui.input_dark_mode(id="mode", mode="light")
+
+ with ui.card():
+ ui.card_header("Dark Mode Example")
+
+ @render.text
+ def current_mode():
+ return f"Current mode: {input.mode()}"
+
+ ui.p("This is a demonstration of dark mode functionality.")
+
+ with ui.div(class_="mt-3"):
+ ui.tags.i(class_="fa-solid fa-moon", style="font-size: 2rem;")
+ ui.span(" Dark Mode Toggle Example", class_="ms-2")
diff --git a/tests/playwright/ai_generated_apps/input_dark_mode/test_input_dark_mode_core_express.py b/tests/playwright/ai_generated_apps/input_dark_mode/test_input_dark_mode_core_express.py
new file mode 100644
index 000000000..3f7436c5c
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_dark_mode/test_input_dark_mode_core_express.py
@@ -0,0 +1,18 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_dark_mode_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test dark mode input
+ dark_mode = controller.InputDarkMode(page, "mode")
+ dark_mode.expect_mode("light") # Default mode is light
+ dark_mode.click() # Toggle dark mode
+ dark_mode.expect_mode("dark")
+ dark_mode.expect_page_mode("dark") # Check if page mode is updated
diff --git a/tests/playwright/ai_generated_apps/input_date/app-core.py b/tests/playwright/ai_generated_apps/input_date/app-core.py
new file mode 100644
index 000000000..8df9cf0ad
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_date/app-core.py
@@ -0,0 +1,139 @@
+from datetime import date
+
+from shiny import App, render, ui
+
+app_ui = ui.page_fluid(
+ ui.h2("Date Input Parameters Demo"),
+ # Basic date input example
+ ui.div(
+ ui.input_date("date1", "Default date input:", value="2024-01-01"),
+ ui.output_text("selected_date1"),
+ ),
+ ui.br(),
+ # Date input with minimum and maximum date constraints
+ ui.div(
+ ui.input_date(
+ "date2",
+ "Date input with min/max:",
+ value=date(2024, 1, 1), # Using a date object instead of string
+ min="2024-01-01",
+ max="2024-12-31",
+ ),
+ ui.output_text("selected_date2"),
+ ),
+ ui.br(),
+ # Date input with custom date format
+ ui.div(
+ ui.input_date(
+ "date3",
+ "Custom format (mm/dd/yy):",
+ value="2024-01-01",
+ format="mm/dd/yy", # Changes how date is displayed
+ ),
+ ui.output_text("selected_date3"),
+ ),
+ ui.br(),
+ # Date input that opens to decade view instead of default month view
+ ui.div(
+ ui.input_date(
+ "date4", "Start in decade view:", value="2024-01-01", startview="decade"
+ ),
+ ui.output_text("selected_date4"),
+ ),
+ ui.br(),
+ # Date input with week starting on Monday (1) instead of Sunday (0)
+ ui.div(
+ ui.input_date(
+ "date5", "Week starts on Monday:", value="2024-01-01", weekstart=1
+ ),
+ ui.output_text("selected_date5"),
+ ),
+ ui.br(),
+ # Date input with German language localization
+ ui.div(
+ ui.input_date("date6", "German language:", value="2024-01-01", language="de"),
+ ui.output_text("selected_date6"),
+ ),
+ ui.br(),
+ # Date input with custom width
+ ui.div(
+ ui.input_date("date7", "Custom width:", value="2024-01-01", width="400px"),
+ ui.output_text("selected_date7"),
+ ),
+ # Date input where calendar doesn't auto-close after selection
+ ui.div(
+ ui.input_date(
+ "date8", "Autoclose disabled:", value="2024-01-01", autoclose=False
+ ),
+ ui.output_text("selected_date8"),
+ ),
+ ui.br(),
+ # Date input with specific dates disabled/unavailable for selection
+ ui.div(
+ ui.input_date(
+ "date9",
+ "Specific dates disabled:",
+ value="2024-01-01",
+ datesdisabled=["2024-01-15", "2024-01-16", "2024-01-17"],
+ ),
+ ui.output_text("selected_date9"),
+ ),
+ ui.br(),
+ # Date input with weekend days disabled/unavailable for selection
+ ui.div(
+ ui.input_date(
+ "date10",
+ "Weekends disabled:",
+ value="2024-01-01",
+ daysofweekdisabled=[0, 6], # 0 = Sunday, 6 = Saturday
+ ),
+ ui.output_text("selected_date10"),
+ ),
+)
+
+
+def server(input, output, session):
+ # Server functions to display the selected date for each input
+ @render.text
+ def selected_date1():
+ return f"Selected date: {input.date1()}"
+
+ @render.text
+ def selected_date2():
+ return f"Selected date: {input.date2()}"
+
+ @render.text
+ def selected_date3():
+ return f"Selected date: {input.date3()}"
+
+ @render.text
+ def selected_date4():
+ return f"Selected date: {input.date4()}"
+
+ @render.text
+ def selected_date5():
+ return f"Selected date: {input.date5()}"
+
+ @render.text
+ def selected_date6():
+ return f"Selected date: {input.date6()}"
+
+ @render.text
+ def selected_date7():
+ return f"Selected date: {input.date7()}"
+
+ @render.text
+ def selected_date8():
+ return f"Selected date: {input.date8()}"
+
+ @render.text
+ def selected_date9():
+ return f"Selected date: {input.date9()}"
+
+ @render.text
+ def selected_date10():
+ return f"Selected date: {input.date10()}"
+
+
+# Create and define the Shiny app with UI and server components
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_date/app-express.py b/tests/playwright/ai_generated_apps/input_date/app-express.py
new file mode 100644
index 000000000..591a6d045
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_date/app-express.py
@@ -0,0 +1,130 @@
+from datetime import date
+
+from shiny.express import input, render, ui
+
+ui.page_opts(title="Date Input Parameters Demo", full_width=True)
+
+# Basic date input
+ui.input_date("date1", "Default date input:", value="2024-01-01")
+
+
+@render.text
+def selected_date1():
+ return f"Selected date: {input.date1()}"
+
+
+ui.br()
+
+# Date input with min and max dates
+ui.input_date(
+ "date2",
+ "Date input with min/max:",
+ value=date(2024, 1, 1),
+ min="2024-01-01",
+ max="2024-12-31",
+)
+
+
+@render.text
+def selected_date2():
+ return f"Selected date: {input.date2()}"
+
+
+ui.br()
+
+# Date input with custom format
+ui.input_date(
+ "date3", "Custom format (mm/dd/yy):", value="2024-01-01", format="mm/dd/yy"
+)
+
+
+@render.text
+def selected_date3():
+ return f"Selected date: {input.date3()}"
+
+
+ui.br()
+
+# Date input with decade view
+ui.input_date("date4", "Start in decade view:", value="2024-01-01", startview="decade")
+
+
+@render.text
+def selected_date4():
+ return f"Selected date: {input.date4()}"
+
+
+ui.br()
+
+# Date input with week starting on Monday
+ui.input_date("date5", "Week starts on Monday:", value="2024-01-01", weekstart=1)
+
+
+@render.text
+def selected_date5():
+ return f"Selected date: {input.date5()}"
+
+
+ui.br()
+
+# Date input with German language
+ui.input_date("date6", "German language:", value="2024-01-01", language="de")
+
+
+@render.text
+def selected_date6():
+ return f"Selected date: {input.date6()}"
+
+
+ui.br()
+
+# Date input with custom width
+ui.input_date("date7", "Custom width:", value="2024-01-01", width="400px")
+
+
+@render.text
+def selected_date7():
+ return f"Selected date: {input.date7()}"
+
+
+ui.br()
+
+# Date input with autoclose disabled
+ui.input_date("date8", "Autoclose disabled:", value="2024-01-01", autoclose=False)
+
+
+@render.text
+def selected_date8():
+ return f"Selected date: {input.date8()}"
+
+
+ui.br()
+
+# Date input with specific dates disabled
+ui.input_date(
+ "date9",
+ "Specific dates disabled:",
+ value="2024-01-01",
+ datesdisabled=["2024-01-15", "2024-01-16", "2024-01-17"],
+)
+
+
+@render.text
+def selected_date9():
+ return f"Selected date: {input.date9()}"
+
+
+ui.br()
+
+# Date input with specific days of week disabled
+ui.input_date(
+ "date10",
+ "Weekends disabled:",
+ value="2024-01-01",
+ daysofweekdisabled=[0, 6], # 0 = Sunday, 6 = Saturday
+)
+
+
+@render.text
+def selected_date10():
+ return f"Selected date: {input.date10()}"
diff --git a/tests/playwright/ai_generated_apps/input_date/test_input_date_core_express.py b/tests/playwright/ai_generated_apps/input_date/test_input_date_core_express.py
new file mode 100644
index 000000000..86966c92e
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_date/test_input_date_core_express.py
@@ -0,0 +1,78 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_date_inputs(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test basic date input
+ date1 = controller.InputDate(page, "date1")
+ date1.expect_label("Default date input:")
+ date1.expect_value("2024-01-01")
+
+ # Test date input with min/max range
+ date2 = controller.InputDate(page, "date2")
+ date2.expect_label("Date input with min/max:")
+ date2.expect_value("2024-01-01")
+ date2.expect_min_date("2024-01-01")
+ date2.expect_max_date("2024-12-31")
+
+ # Test date input with custom format
+ date3 = controller.InputDate(page, "date3")
+ date3.expect_label("Custom format (mm/dd/yy):")
+ date3.expect_format("mm/dd/yy")
+ date3.expect_value("01/01/24")
+
+ # Test date input with decade view
+ date4 = controller.InputDate(page, "date4")
+ date4.expect_label("Start in decade view:")
+ date4.expect_value("2024-01-01")
+ date4.expect_startview("decade")
+
+ # Test date input with Monday start
+ date5 = controller.InputDate(page, "date5")
+ date5.expect_label("Week starts on Monday:")
+ date5.expect_value("2024-01-01")
+ date5.expect_weekstart("1")
+
+ # Test date input with German language
+ date6 = controller.InputDate(page, "date6")
+ date6.expect_label("German language:")
+ date6.expect_value("2024-01-01")
+ date6.expect_language("de")
+
+ # Test date input with custom width
+ date7 = controller.InputDate(page, "date7")
+ date7.expect_label("Custom width:")
+ date7.expect_value("2024-01-01")
+ date7.expect_width("400px")
+
+ # Test date input without autoclose
+ date8 = controller.InputDate(page, "date8")
+ date8.expect_label("Autoclose disabled:")
+ date8.expect_value("2024-01-01")
+ date8.expect_autoclose("false")
+
+ # Test date input with disabled dates
+ date9 = controller.InputDate(page, "date9")
+ date9.expect_label("Specific dates disabled:")
+ date9.expect_value("2024-01-01")
+ date9.expect_datesdisabled(["2024-01-15", "2024-01-16", "2024-01-17"])
+
+ # Test date input with disabled days of week
+ date10 = controller.InputDate(page, "date10")
+ date10.expect_label("Weekends disabled:")
+ date10.expect_value("2024-01-01")
+ date10.expect_daysofweekdisabled([0, 6])
+
+ # Test setting new values
+ date1.set("2024-02-01")
+ date1.expect_value("2024-02-01")
+
+ date2.set("2024-06-15")
+ date2.expect_value("2024-06-15")
diff --git a/tests/playwright/ai_generated_apps/input_date_range/app-core.py b/tests/playwright/ai_generated_apps/input_date_range/app-core.py
new file mode 100644
index 000000000..99ee33cf2
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_date_range/app-core.py
@@ -0,0 +1,76 @@
+from datetime import date
+
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fluid(
+ # Add descriptive text
+ ui.h2("Date Range Input Example"),
+ ui.markdown(
+ """
+ This example demonstrates a date range input with various customization options:
+ - Custom date format (mm/dd/yyyy)
+ - Restricted date range (2020-2025)
+ - Week starting on Monday
+ - Custom separator
+ """
+ ),
+ # Layout wrapper
+ ui.layout_column_wrap(
+ # Date range input with all parameters
+ ui.input_date_range(
+ id="date_range",
+ label="Select Date Range",
+ start=date(2023, 1, 1), # Initial start date
+ end=date(2023, 12, 31), # Initial end date
+ min=date(2020, 1, 1), # Minimum allowed date
+ max=date(2025, 12, 31), # Maximum allowed date
+ format="mm/dd/yyyy", # Display format
+ startview="decade", # Initial view when opened
+ weekstart=1, # Week starts on Monday (0=Sunday, 1=Monday)
+ language="en", # Language for month/day names
+ separator=" → ", # Custom separator between dates
+ width="100%", # Width of the input
+ autoclose=True, # Close the calendar when a date is selected
+ ),
+ # Card to display selected range
+ ui.card(
+ ui.card_header("Selected Date Range"),
+ ui.output_text("selected_range"),
+ ),
+ ),
+ # Additional date info output
+ ui.output_ui("date_info"),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @render.text
+ def selected_range():
+ date_range = input.date_range()
+ if date_range is None:
+ return "No dates selected"
+
+ start, end = date_range
+ return f"Start date: {start}\nEnd date: {end}"
+
+ @render.ui
+ def date_info():
+ date_range = input.date_range()
+ if date_range is None:
+ return ui.p("Please select a date range", class_="text-muted")
+
+ start, end = date_range
+ days = (end - start).days
+
+ return ui.div(
+ ui.p(f"Number of days selected: {days}"),
+ ui.p(f"Start day of week: {start.strftime('%A')}"),
+ ui.p(f"End day of week: {end.strftime('%A')}"),
+ class_="mt-3",
+ )
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_date_range/app-express.py b/tests/playwright/ai_generated_apps/input_date_range/app-express.py
new file mode 100644
index 000000000..bbca2566b
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_date_range/app-express.py
@@ -0,0 +1,64 @@
+from datetime import date
+
+from shiny.express import input, render, ui
+
+# Add some descriptive text
+ui.h2("Date Range Input Example")
+ui.markdown(
+ """
+This example demonstrates a date range input with various customization options:
+- Custom date format (mm/dd/yyyy)
+- Restricted date range (2020-2025)
+- Week starting on Monday
+- Custom separator
+"""
+)
+
+with ui.layout_column_wrap():
+ # Create a date range input with all possible parameters
+ ui.input_date_range(
+ id="date_range",
+ label="Select Date Range",
+ start=date(2023, 1, 1), # Initial start date
+ end=date(2023, 12, 31), # Initial end date
+ min=date(2020, 1, 1), # Minimum allowed date
+ max=date(2025, 12, 31), # Maximum allowed date
+ format="mm/dd/yyyy", # Display format
+ startview="decade", # Initial view when opened
+ weekstart=1, # Week starts on Monday (0=Sunday, 1=Monday)
+ language="en", # Language for month/day names
+ separator=" → ", # Custom separator between dates
+ width="100%", # Width of the input
+ autoclose=True, # Close the calendar when a date is selected
+ )
+
+ # Add a card to display the selected range
+ with ui.card():
+ ui.card_header("Selected Date Range")
+
+ @render.text
+ def selected_range():
+ date_range = input.date_range()
+ if date_range is None:
+ return "No dates selected"
+
+ start, end = date_range
+ return f"Start date: {start}\nEnd date: {end}"
+
+
+# Add some additional information about the selection
+@render.ui
+def date_info():
+ date_range = input.date_range()
+ if date_range is None:
+ return ui.p("Please select a date range", class_="text-muted")
+
+ start, end = date_range
+ days = (end - start).days
+
+ return ui.div(
+ ui.p(f"Number of days selected: {days}"),
+ ui.p(f"Start day of week: {start.strftime('%A')}"),
+ ui.p(f"End day of week: {end.strftime('%A')}"),
+ class_="mt-3",
+ )
diff --git a/tests/playwright/ai_generated_apps/input_date_range/test_input_date_range_core_express.py b/tests/playwright/ai_generated_apps/input_date_range/test_input_date_range_core_express.py
new file mode 100644
index 000000000..dbf081bf3
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_date_range/test_input_date_range_core_express.py
@@ -0,0 +1,47 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_date_range_input(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Initialize the date range input controller
+ date_range = controller.InputDateRange(page, "date_range")
+
+ # Test selected_range output
+ selected_range = controller.OutputText(page, "selected_range")
+ selected_range.expect_value("Start date: 2023-01-01\nEnd date: 2023-12-31")
+
+ # Test initial properties
+ date_range.expect_label("Select Date Range")
+ date_range.expect_value(("01/01/2023", "12/31/2023")) # Test initial values
+ date_range.expect_min_date("2020-01-01") # Test minimum date
+ date_range.expect_max_date("2025-12-31") # Test maximum date
+ date_range.expect_format("mm/dd/yyyy") # Test date format
+ date_range.expect_startview("decade") # Test start view
+ date_range.expect_weekstart("1") # Test week start (Monday)
+ date_range.expect_language("en") # Test language setting
+ date_range.expect_separator(" → ") # Test separator
+ date_range.expect_width("100%") # Test width
+ date_range.expect_autoclose("true") # Test autoclose
+
+ # Test setting new values
+ date_range.set(("2023-06-01", "2023-06-30"))
+ date_range.expect_value(("2023-06-01", "2023-06-30"))
+
+ # Test setting only start date
+ date_range.set(("2023-07-01", None))
+ date_range.expect_value(("2023-07-01", "2023-06-30"))
+
+ # Test setting only end date
+ date_range.set((None, "2023-07-31"))
+ date_range.expect_value(("2023-07-01", "2023-07-31"))
+
+ # Test setting dates at the boundaries
+ date_range.set(("2020-01-01", "2025-12-31")) # Min and max dates
+ date_range.expect_value(("2020-01-01", "2025-12-31"))
diff --git a/tests/playwright/ai_generated_apps/input_file/app-core.py b/tests/playwright/ai_generated_apps/input_file/app-core.py
new file mode 100644
index 000000000..ce6c9cd5d
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_file/app-core.py
@@ -0,0 +1,80 @@
+import pandas as pd
+
+from shiny import App, reactive, render, ui
+from shiny.types import FileInfo
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Main card containing file input and table
+ ui.card(
+ ui.card_header("File Upload Demo"),
+ # File input with all possible parameters
+ ui.input_file(
+ id="file1",
+ label="Upload File",
+ multiple=True,
+ accept=[".csv", ".txt", "text/plain", "application/pdf", "image/*"],
+ button_label="Choose Files...",
+ placeholder="Multiple files can be selected",
+ width="400px",
+ capture="user",
+ ),
+ # Output for the data grid
+ ui.output_data_frame("file_table"),
+ # Instructions card
+ ui.card(
+ ui.markdown(
+ """
+ ### Instructions:
+ 1. Click 'Choose Files...' to select one or more files
+ 2. Supported file types: CSV, TXT, PDF, and images
+ 3. File information will appear in the table below
+ 4. Use filters to search through uploaded files
+ """
+ )
+ ),
+ )
+)
+
+
+# Define the server
+def server(input, output, session):
+ # Reactive calculation for parsing files
+ @reactive.calc
+ def parse_files():
+ files: list[FileInfo] | None = input.file1()
+ if not files:
+ return pd.DataFrame()
+
+ # Create a DataFrame with file information
+ file_info = []
+ for file in files:
+ file_info.append(
+ {
+ "Name": file["name"],
+ "Size (bytes)": file["size"],
+ "Type": file["type"],
+ }
+ )
+ return pd.DataFrame(file_info)
+
+ # Render the data grid
+ @output
+ @render.data_frame
+ def file_table():
+ df = parse_files()
+ if df.empty:
+ return render.DataGrid(
+ pd.DataFrame(columns=["Name", "Size (bytes)", "Type"]),
+ height="300px",
+ width="100%",
+ filters=True,
+ selection_mode="row",
+ )
+ return render.DataGrid(
+ df, height="300px", width="100%", filters=True, selection_mode="row"
+ )
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_file/app-express.py b/tests/playwright/ai_generated_apps/input_file/app-express.py
new file mode 100644
index 000000000..eafb7f756
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_file/app-express.py
@@ -0,0 +1,68 @@
+import pandas as pd
+
+from shiny import reactive
+from shiny.express import input, render, ui
+from shiny.types import FileInfo
+
+# Set page options
+ui.page_opts(title="File Input Demo", fillable=True)
+
+with ui.card():
+ ui.card_header("File Upload Demo")
+
+ # File input with all possible parameters
+ ui.input_file(
+ id="file1",
+ label="Upload File",
+ multiple=True,
+ accept=[".csv", ".txt", "text/plain", "application/pdf", "image/*"],
+ button_label="Choose Files...",
+ placeholder="Multiple files can be selected",
+ width="400px",
+ capture="user",
+ )
+
+ @reactive.calc
+ def parse_files():
+ files: list[FileInfo] | None = input.file1()
+ if not files:
+ return pd.DataFrame()
+
+ # Create a DataFrame with file information
+ file_info = []
+ for file in files:
+ file_info.append(
+ {
+ "Name": file["name"],
+ "Size (bytes)": file["size"],
+ "Type": file["type"],
+ }
+ )
+ return pd.DataFrame(file_info)
+
+ @render.data_frame
+ def file_table():
+ df = parse_files()
+ if df.empty:
+ return render.DataGrid(
+ pd.DataFrame(columns=["Name", "Size (bytes)", "Type"]),
+ height="300px",
+ width="100%",
+ filters=True,
+ selection_mode="row",
+ )
+ return render.DataGrid(
+ df, height="300px", width="100%", filters=True, selection_mode="row"
+ )
+
+ # Add some instructions
+ with ui.card():
+ ui.markdown(
+ """
+ ### Instructions:
+ 1. Click 'Choose Files...' to select one or more files
+ 2. Supported file types: CSV, TXT, PDF, and images
+ 3. File information will appear in the table below
+ 4. Use filters to search through uploaded files
+ """
+ )
diff --git a/tests/playwright/ai_generated_apps/input_file/test_input_file_core_express.py b/tests/playwright/ai_generated_apps/input_file/test_input_file_core_express.py
new file mode 100644
index 000000000..45da52e04
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_file/test_input_file_core_express.py
@@ -0,0 +1,58 @@
+from playwright.sync_api import FilePayload, Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_file_input_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test file input component
+ file_input = controller.InputFile(page, "file1")
+
+ # Test initial state
+ file_input.expect_label("Upload File")
+ file_input.expect_multiple(True)
+ file_input.expect_accept(
+ [".csv", ".txt", "text/plain", "application/pdf", "image/*"]
+ )
+ file_input.expect_button_label("Choose Files...")
+ file_input.expect_width("400px")
+ file_input.expect_capture("user")
+
+ # Test data frame output initial state
+ file_table = controller.OutputDataFrame(page, "file_table")
+ file_table.expect_column_labels(["Name", "Size (bytes)", "Type"])
+ file_table.expect_nrow(0)
+ file_table.expect_ncol(3)
+
+ # Note: The file content is a CSV with a header and some data
+ # Simulate uploading a CSV file
+ file_info: FilePayload = {
+ "name": "users.csv",
+ "mimeType": "text/csv",
+ "buffer": b',user_id,name,email\n1,Alice,alice@example.com\n2,"Bob, Los Angeles", bob\n',
+ }
+
+ file_input.set(file_info)
+ file_input.expect_complete()
+
+ # After upload, check if table is updated
+ file_table.expect_nrow(1) # Should show one row for the uploaded file
+ file_table.expect_ncol(3) # Should still have 3 columns
+
+ # simulate uploading multiple files
+ file_info2 = file_info.copy()
+ file_info2["name"] = "users2.csv"
+ file_info2["buffer"] = (
+ b",user_id,name,email\n3,Charlie,charlie@example.com\n4,Dave,dave@example.com\n"
+ )
+ file_input.set([file_info, file_info2])
+ file_input.expect_complete()
+
+ # After uploading multiple files, check if table is updated
+ file_table.expect_nrow(2) # Should show two rows for the uploaded files
+ file_table.expect_ncol(3) # Should still have 3 columns
diff --git a/tests/playwright/ai_generated_apps/input_numeric/app-core.py b/tests/playwright/ai_generated_apps/input_numeric/app-core.py
new file mode 100644
index 000000000..3471a8b65
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_numeric/app-core.py
@@ -0,0 +1,62 @@
+from shiny import App, render, ui
+
+app_ui = ui.page_fluid(
+ ui.layout_column_wrap(
+ ui.card(
+ ui.card_header("Basic Numeric Input"),
+ ui.input_numeric(id="basic", label="Basic numeric input", value=10),
+ ui.output_text("basic_value"),
+ ),
+ ui.card(
+ ui.card_header("With Min/Max"),
+ ui.input_numeric(
+ id="with_min_max",
+ label="With min and max values",
+ value=5,
+ min=0,
+ max=10,
+ ),
+ ui.output_text("minmax_value"),
+ ),
+ ui.card(
+ ui.card_header("With Step Size"),
+ ui.input_numeric(
+ id="with_step", label="With step size", value=0, min=0, max=100, step=5
+ ),
+ ui.output_text("step_value"),
+ ),
+ ui.card(
+ ui.card_header("With Custom Width"),
+ ui.input_numeric(
+ id="with_width", label="With custom width", value=42, width="200px"
+ ),
+ ui.output_text("width_value"),
+ ),
+ width=1 / 2,
+ )
+)
+
+
+def server(input, output, session):
+ @output
+ @render.text
+ def basic_value():
+ return f"Current value: {input.basic()}"
+
+ @output
+ @render.text
+ def minmax_value():
+ return f"Current value: {input.with_min_max()}"
+
+ @output
+ @render.text
+ def step_value():
+ return f"Current value: {input.with_step()}"
+
+ @output
+ @render.text
+ def width_value():
+ return f"Current value: {input.with_width()}"
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_numeric/app-express.py b/tests/playwright/ai_generated_apps/input_numeric/app-express.py
new file mode 100644
index 000000000..7c3877994
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_numeric/app-express.py
@@ -0,0 +1,43 @@
+from shiny.express import input, render, ui
+
+# Set page title
+ui.page_opts(full_width=True)
+
+with ui.layout_column_wrap(width=1 / 2):
+ with ui.card():
+ ui.card_header("Basic Numeric Input")
+ ui.input_numeric(id="basic", label="Basic numeric input", value=10)
+
+ @render.text
+ def basic_value():
+ return f"Current value: {input.basic()}"
+
+ with ui.card():
+ ui.card_header("With Min/Max")
+ ui.input_numeric(
+ id="with_min_max", label="With min and max values", value=5, min=0, max=10
+ )
+
+ @render.text
+ def minmax_value():
+ return f"Current value: {input.with_min_max()}"
+
+ with ui.card():
+ ui.card_header("With Step Size")
+ ui.input_numeric(
+ id="with_step", label="With step size", value=0, min=0, max=100, step=5
+ )
+
+ @render.text
+ def step_value():
+ return f"Current value: {input.with_step()}"
+
+ with ui.card():
+ ui.card_header("With Custom Width")
+ ui.input_numeric(
+ id="with_width", label="With custom width", value=42, width="200px"
+ )
+
+ @render.text
+ def width_value():
+ return f"Current value: {input.with_width()}"
diff --git a/tests/playwright/ai_generated_apps/input_numeric/test_input_numeric_core_express.py b/tests/playwright/ai_generated_apps/input_numeric/test_input_numeric_core_express.py
new file mode 100644
index 000000000..82a97d6a8
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_numeric/test_input_numeric_core_express.py
@@ -0,0 +1,53 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_numeric_inputs(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test basic numeric input
+ basic = controller.InputNumeric(page, "basic")
+ basic_output = controller.OutputText(page, "basic_value")
+
+ basic.expect_label("Basic numeric input")
+ basic.expect_value("10")
+ basic_output.expect_value("Current value: 10")
+
+ # Test with new value
+ basic.set("20")
+ basic_output.expect_value("Current value: 20")
+
+ # Test numeric input with min/max
+ with_min_max = controller.InputNumeric(page, "with_min_max")
+ minmax_output = controller.OutputText(page, "minmax_value")
+
+ with_min_max.expect_label("With min and max values")
+ with_min_max.expect_value("5")
+ with_min_max.expect_min("0")
+ with_min_max.expect_max("10")
+ minmax_output.expect_value("Current value: 5")
+
+ # Test with step size
+ with_step = controller.InputNumeric(page, "with_step")
+ step_output = controller.OutputText(page, "step_value")
+
+ with_step.expect_label("With step size")
+ with_step.expect_value("0")
+ with_step.expect_min("0")
+ with_step.expect_max("100")
+ with_step.expect_step("5")
+ step_output.expect_value("Current value: 0")
+
+ # Test with custom width
+ with_width = controller.InputNumeric(page, "with_width")
+ width_output = controller.OutputText(page, "width_value")
+
+ with_width.expect_label("With custom width")
+ with_width.expect_value("42")
+ with_width.expect_width("200px")
+ width_output.expect_value("Current value: 42")
diff --git a/tests/playwright/ai_generated_apps/input_password/app-core.py b/tests/playwright/ai_generated_apps/input_password/app-core.py
new file mode 100644
index 000000000..e36f5c381
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_password/app-core.py
@@ -0,0 +1,46 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fluid(
+ # Card container
+ ui.card(
+ ui.card_header("Password Input Example"),
+ # Create password input
+ ui.input_password(
+ id="pwd",
+ label="Enter Password",
+ value="default123",
+ width="300px",
+ placeholder="Type your password here",
+ ),
+ # Output for password length
+ ui.output_text("password_length"),
+ # Output for masked password
+ ui.output_text("password_masked"),
+ )
+)
+
+
+# Define the server
+def server(input, output, session):
+ # Show current input length
+ @output
+ @render.text
+ def password_length():
+ pwd = input.pwd()
+ if not pwd:
+ return "No password entered"
+ return f"Password length: {len(pwd)} characters"
+
+ # Show masked password
+ @output
+ @render.text
+ def password_masked():
+ pwd = input.pwd()
+ if not pwd:
+ return ""
+ return f"Masked password: {'*' * len(pwd)}"
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_password/app-express.py b/tests/playwright/ai_generated_apps/input_password/app-express.py
new file mode 100644
index 000000000..335224daa
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_password/app-express.py
@@ -0,0 +1,32 @@
+from shiny.express import input, render, ui
+
+# Set page title
+ui.page_opts(full_width=True)
+
+with ui.card():
+ ui.card_header("Password Input Example")
+
+ # Create password input
+ ui.input_password(
+ id="pwd",
+ label="Enter Password",
+ value="default123",
+ width="300px",
+ placeholder="Type your password here",
+ )
+
+ # Show current input length
+ @render.text
+ def password_length():
+ pwd = input.pwd()
+ if not pwd:
+ return "No password entered"
+ return f"Password length: {len(pwd)} characters"
+
+ # Show masked password
+ @render.text
+ def password_masked():
+ pwd = input.pwd()
+ if not pwd:
+ return ""
+ return f"Masked password: {'*' * len(pwd)}"
diff --git a/tests/playwright/ai_generated_apps/input_password/test_input_password_core_express.py b/tests/playwright/ai_generated_apps/input_password/test_input_password_core_express.py
new file mode 100644
index 000000000..3abf1e531
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_password/test_input_password_core_express.py
@@ -0,0 +1,32 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_password_input(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test password input
+ pwd = controller.InputPassword(page, "pwd")
+ pwd.expect_label("Enter Password")
+ pwd.expect_value("default123")
+ pwd.expect_width("300px")
+ pwd.expect_placeholder("Type your password here")
+
+ # Test password length output
+ pwd_length = controller.OutputText(page, "password_length")
+ pwd_length.expect_value("Password length: 10 characters")
+
+ # Test masked password output
+ pwd_masked = controller.OutputText(page, "password_masked")
+ pwd_masked.expect_value("Masked password: **********")
+
+ # Test setting new password
+ pwd.set("newpass12")
+ pwd.expect_value("newpass12")
+ pwd_length.expect_value("Password length: 9 characters")
+ pwd_masked.expect_value("Masked password: *********")
diff --git a/tests/playwright/ai_generated_apps/input_radio_buttons/app-core.py b/tests/playwright/ai_generated_apps/input_radio_buttons/app-core.py
new file mode 100644
index 000000000..1c939acc6
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_radio_buttons/app-core.py
@@ -0,0 +1,40 @@
+from shiny import App, render, ui
+
+# Create sample choices with HTML content
+choices = {
+ "choice1": ui.span("Choice 1", style="color: red;"),
+ "choice2": ui.span("Choice 2", style="color: blue;"),
+ "choice3": ui.span("Choice 3", style="color: green;"),
+}
+
+# Define the UI
+app_ui = ui.page_fluid(
+ ui.card(
+ ui.card_header("Radio Buttons Example"),
+ # Create radio buttons with all possible parameters
+ ui.input_radio_buttons(
+ id="radio_demo", # Required: unique identifier
+ label="Demo Radio Group", # Required: label text
+ choices=choices, # Required: choices as dict with HTML content
+ selected="choice1", # Optional: initial selected value
+ inline=True, # Optional: display buttons inline
+ width="300px", # Optional: CSS width
+ ),
+ # Add some spacing
+ ui.br(),
+ # Output for displaying selection
+ ui.output_text("selection"),
+ )
+)
+
+
+# Define the server
+def server(input, output, session):
+ @output
+ @render.text
+ def selection():
+ return f"You selected: {input.radio_demo()}"
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_radio_buttons/app-express.py b/tests/playwright/ai_generated_apps/input_radio_buttons/app-express.py
new file mode 100644
index 000000000..9ab37afe8
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_radio_buttons/app-express.py
@@ -0,0 +1,33 @@
+from shiny.express import input, render, ui
+
+# Set page title
+ui.page_opts(full_width=True)
+
+# Create sample choices with HTML content
+choices = {
+ "choice1": ui.span("Choice 1", style="color: red;"),
+ "choice2": ui.span("Choice 2", style="color: blue;"),
+ "choice3": ui.span("Choice 3", style="color: green;"),
+}
+
+# Create a card to contain the radio buttons and output
+with ui.card():
+ ui.card_header("Radio Buttons Example")
+
+ # Create radio buttons with all possible parameters
+ ui.input_radio_buttons(
+ id="radio_demo", # Required: unique identifier
+ label="Demo Radio Group", # Required: label text
+ choices=choices, # Required: choices as dict with HTML content
+ selected="choice1", # Optional: initial selected value
+ inline=True, # Optional: display buttons inline
+ width="300px", # Optional: CSS width
+ )
+
+ # Add some spacing
+ ui.br()
+
+ # Display the current selection
+ @render.text
+ def selection():
+ return f"You selected: {input.radio_demo()}"
diff --git a/tests/playwright/ai_generated_apps/input_radio_buttons/test_input_radio_buttons_core_express.py b/tests/playwright/ai_generated_apps/input_radio_buttons/test_input_radio_buttons_core_express.py
new file mode 100644
index 000000000..0972fcf7e
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_radio_buttons/test_input_radio_buttons_core_express.py
@@ -0,0 +1,34 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_radio_buttons_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test radio buttons input
+ radio_buttons = controller.InputRadioButtons(page, "radio_demo")
+ selection_output = controller.OutputText(page, "selection")
+
+ # Test initial state
+ radio_buttons.expect_label("Demo Radio Group")
+ radio_buttons.expect_choices(["choice1", "choice2", "choice3"])
+ radio_buttons.expect_selected("choice1") # Test default selection
+ radio_buttons.expect_inline(True)
+ radio_buttons.expect_width("300px")
+ selection_output.expect_value("You selected: choice1")
+
+ # Test changing selections
+ radio_buttons.set("choice2")
+ selection_output.expect_value("You selected: choice2")
+
+ radio_buttons.set("choice3")
+ selection_output.expect_value("You selected: choice3")
+
+ # Test going back to first choice
+ radio_buttons.set("choice1")
+ selection_output.expect_value("You selected: choice1")
diff --git a/tests/playwright/ai_generated_apps/input_select/app-core.py b/tests/playwright/ai_generated_apps/input_select/app-core.py
new file mode 100644
index 000000000..e0d8ac126
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_select/app-core.py
@@ -0,0 +1,112 @@
+from shiny import App, render, ui
+
+# Sample data for different types of choices
+simple_choices = ["A", "B", "C", "D"]
+dict_choices = {"a": "Option A", "b": "Option B", "c": "Option C"}
+grouped_choices = {
+ "Group 1": {"g1a": "Group 1 - A", "g1b": "Group 1 - B"},
+ "Group 2": {"g2a": "Group 2 - A", "g2b": "Group 2 - B"},
+}
+
+app_ui = ui.page_fillable(
+ ui.layout_column_wrap(
+ # Basic select with simple choices
+ ui.card(
+ ui.card_header("Basic Select"),
+ ui.input_select(
+ id="select1",
+ label="Basic select (simple list)",
+ choices=simple_choices,
+ selected="A",
+ ),
+ ui.output_text("selected_value1"),
+ ),
+ # Select with dictionary choices
+ ui.card(
+ ui.card_header("Dictionary Choices"),
+ ui.input_select(
+ id="select2",
+ label="Select with dictionary choices",
+ choices=dict_choices,
+ selected="a",
+ ),
+ ui.output_text("selected_value2"),
+ ),
+ # Select with grouped choices
+ ui.card(
+ ui.card_header("Grouped Choices"),
+ ui.input_select(
+ id="select3",
+ label="Select with grouped choices",
+ choices=grouped_choices,
+ selected="g1a",
+ ),
+ ui.output_text("selected_value3"),
+ ),
+ # Multiple select
+ ui.card(
+ ui.card_header("Multiple Select"),
+ ui.input_select(
+ id="select4",
+ label="Multiple select",
+ choices=simple_choices,
+ selected=["A", "B"],
+ multiple=True,
+ ),
+ ui.output_text("selected_value4"),
+ ),
+ # Select with custom width
+ ui.card(
+ ui.card_header("Custom Width"),
+ ui.input_select(
+ id="select5",
+ label="Select with custom width",
+ choices=simple_choices,
+ width="200px",
+ ),
+ ui.output_text("selected_value5"),
+ ),
+ # Select with size parameter
+ ui.card(
+ ui.card_header("Box Style"),
+ ui.input_select(
+ id="select6",
+ label="Select with size parameter",
+ choices=simple_choices,
+ size="4", # Shows 4 items at once
+ ),
+ ui.output_text("selected_value6"),
+ ),
+ width="400px",
+ ),
+)
+
+
+def server(input, output, session):
+
+ @render.text
+ def selected_value1():
+ return f"Selected: {input.select1()}"
+
+ @render.text
+ def selected_value2():
+ return f"Selected: {input.select2()}"
+
+ @render.text
+ def selected_value3():
+ return f"Selected: {input.select3()}"
+
+ @render.text
+ def selected_value4():
+ return f"Selected: {input.select4()}"
+
+ @render.text
+ def selected_value5():
+ return f"Selected: {input.select5()}"
+
+ @render.text
+ def selected_value6():
+ return f"Selected: {input.select6()}"
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_select/app-express.py b/tests/playwright/ai_generated_apps/input_select/app-express.py
new file mode 100644
index 000000000..cabf7575e
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_select/app-express.py
@@ -0,0 +1,98 @@
+from shiny.express import input, render, ui
+
+# Sample data for different types of choices
+simple_choices = ["A", "B", "C", "D"]
+dict_choices = {"a": "Option A", "b": "Option B", "c": "Option C"}
+grouped_choices = {
+ "Group 1": {"g1a": "Group 1 - A", "g1b": "Group 1 - B"},
+ "Group 2": {"g2a": "Group 2 - A", "g2b": "Group 2 - B"},
+}
+
+# Page options
+ui.page_opts(fillable=True)
+
+with ui.layout_column_wrap(width="400px"):
+ # Basic select with simple choices
+ with ui.card():
+ ui.card_header("Basic Select")
+ ui.input_select(
+ id="select1",
+ label="Basic select (simple list)",
+ choices=simple_choices,
+ selected="A",
+ )
+
+ @render.text
+ def selected_value1():
+ return f"Selected: {input.select1()}"
+
+ # Select with dictionary choices
+ with ui.card():
+ ui.card_header("Dictionary Choices")
+ ui.input_select(
+ id="select2",
+ label="Select with dictionary choices",
+ choices=dict_choices,
+ selected="a",
+ )
+
+ @render.text
+ def selected_value2():
+ return f"Selected: {input.select2()}"
+
+ # Select with grouped choices
+ with ui.card():
+ ui.card_header("Grouped Choices")
+ ui.input_select(
+ id="select3",
+ label="Select with grouped choices",
+ choices=grouped_choices,
+ selected="g1a",
+ )
+
+ @render.text
+ def selected_value3():
+ return f"Selected: {input.select3()}"
+
+ # Multiple select
+ with ui.card():
+ ui.card_header("Multiple Select")
+ ui.input_select(
+ id="select4",
+ label="Multiple select",
+ choices=simple_choices,
+ selected=["A", "B"],
+ multiple=True,
+ )
+
+ @render.text
+ def selected_value4():
+ return f"Selected: {input.select4()}"
+
+ # Select with custom width
+ with ui.card():
+ ui.card_header("Custom Width")
+ ui.input_select(
+ id="select5",
+ label="Select with custom width",
+ choices=simple_choices,
+ width="200px",
+ )
+
+ @render.text
+ def selected_value5():
+ return f"Selected: {input.select5()}"
+
+ # Select with size parameter
+ with ui.card():
+ ui.card_header("Box Style")
+ ui.input_select(
+ id="select6",
+ label="Select with size parameter",
+ choices=simple_choices,
+ size="4", # Shows 4 items at once
+ )
+
+ @render.text
+ def selected_value6():
+ return f"Selected: {input.select6()}"
diff --git a/tests/playwright/ai_generated_apps/input_select/test_input_select_core_express.py b/tests/playwright/ai_generated_apps/input_select/test_input_select_core_express.py
new file mode 100644
index 000000000..8b0da4f9f
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_select/test_input_select_core_express.py
@@ -0,0 +1,70 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_input_select_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test basic select with simple choices
+ select1 = controller.InputSelect(page, "select1")
+ select1.expect_label("Basic select (simple list)")
+ select1.expect_choices(["A", "B", "C", "D"])
+ select1.expect_selected("A") # Test initial selection
+ select1.expect_multiple(False)
+ select1_value = controller.OutputText(page, "selected_value1")
+ select1_value.expect_value("Selected: A")
+
+ # Test select with dictionary choices
+ select2 = controller.InputSelect(page, "select2")
+ select2.expect_label("Select with dictionary choices")
+ select2.expect_choices(["a", "b", "c"])
+ select2.expect_choice_labels(["Option A", "Option B", "Option C"])
+ select2.expect_selected("a") # Test initial selection
+ select2.expect_multiple(False)
+ select2_value = controller.OutputText(page, "selected_value2")
+ select2_value.expect_value("Selected: a")
+
+ # Test select with grouped choices
+ select3 = controller.InputSelect(page, "select3")
+ select3.expect_label("Select with grouped choices")
+ select3.expect_choices(["g1a", "g1b", "g2a", "g2b"])
+ select3.expect_choice_labels(
+ ["Group 1 - A", "Group 1 - B", "Group 2 - A", "Group 2 - B"]
+ )
+ select3.expect_choice_groups(["Group 1", "Group 2"])
+ select3.expect_selected("g1a") # Test initial selection
+ select3.expect_multiple(False)
+ select3_value = controller.OutputText(page, "selected_value3")
+ select3_value.expect_value("Selected: g1a")
+
+ # Test multiple select
+ select4 = controller.InputSelect(page, "select4")
+ select4.expect_label("Multiple select")
+ select4.expect_choices(["A", "B", "C", "D"])
+ select4.expect_selected(["A", "B"]) # Test initial multiple selection
+ select4.expect_multiple(True)
+ select4_value = controller.OutputText(page, "selected_value4")
+ select4_value.expect_value("Selected: ('A', 'B')")
+
+ # Test select with custom width
+ select5 = controller.InputSelect(page, "select5")
+ select5.expect_label("Select with custom width")
+ select5.expect_choices(["A", "B", "C", "D"])
+ select5.expect_width("200px")
+ select5.expect_multiple(False)
+ select5_value = controller.OutputText(page, "selected_value5")
+ select5_value.expect_value("Selected: A")
+
+ # Test select with size parameter
+ select6 = controller.InputSelect(page, "select6")
+ select6.expect_label("Select with size parameter")
+ select6.expect_choices(["A", "B", "C", "D"])
+ select6.expect_size("4")
+ select6.expect_multiple(False)
+ select6_value = controller.OutputText(page, "selected_value6")
+ select6_value.expect_value("Selected: A")
diff --git a/tests/playwright/ai_generated_apps/input_slider/app-core.py b/tests/playwright/ai_generated_apps/input_slider/app-core.py
new file mode 100644
index 000000000..f67be899a
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_slider/app-core.py
@@ -0,0 +1,162 @@
+from datetime import datetime
+from zoneinfo import ZoneInfo
+
+from shiny import App, reactive, render, ui
+
+# Define a consistent timezone
+TIMEZONE = ZoneInfo("UTC")
+
+app_ui = ui.page_fluid(
+ ui.panel_title("Slider Parameters Demo"),
+ ui.layout_column_wrap(
+ # Numeric Slider - basic parameters
+ ui.card(
+ ui.card_header("Basic Numeric Slider"),
+ ui.input_slider("slider1", "Min, max, value", min=0, max=100, value=50),
+ ui.output_text("value1"),
+ ),
+ # Numeric Slider with step
+ ui.card(
+ ui.card_header("Step Parameter"),
+ ui.input_slider(
+ "slider2", "Step size = 10", min=0, max=100, value=50, step=10
+ ),
+ ui.output_text("value2"),
+ ),
+ # Range Slider
+ ui.card(
+ ui.card_header("Range Slider"),
+ ui.input_slider(
+ "slider3", "Select a range", min=0, max=100, value=(30, 70)
+ ),
+ ui.output_text("value3"),
+ ),
+ # Date Slider
+ ui.card(
+ ui.card_header("Date Slider"),
+ ui.input_slider(
+ "slider4",
+ "Select a date",
+ min=datetime(2023, 1, 1, 0, 0, tzinfo=TIMEZONE),
+ max=datetime(2023, 12, 31, 0, 0, tzinfo=TIMEZONE),
+ value=datetime(2023, 6, 15, 12, 30, tzinfo=TIMEZONE),
+ time_format="%Y-%m-%d",
+ ),
+ ui.output_text("value4"),
+ ),
+ # Animated Slider
+ ui.card(
+ ui.card_header("Animated Slider"),
+ ui.input_slider(
+ "slider5", "With animation", min=0, max=100, value=50, animate=True
+ ),
+ ui.output_text("value5"),
+ ),
+ # Slider with custom formatting
+ ui.card(
+ ui.card_header("Custom Formatting"),
+ ui.input_slider(
+ "slider6",
+ "With prefix and suffix",
+ min=0,
+ max=100,
+ value=50,
+ pre="$",
+ post="%",
+ sep=",",
+ ),
+ ui.output_text("value6"),
+ ),
+ # Slider with ticks
+ ui.card(
+ ui.card_header("Ticks Display"),
+ ui.input_slider(
+ "slider7", "With tick marks", min=0, max=100, value=50, ticks=True
+ ),
+ ui.output_text("value7"),
+ ),
+ # Date Range Slider with drag_range
+ ui.card(
+ ui.card_header("Date Range"),
+ ui.input_slider(
+ "slider9",
+ "Draggable range",
+ min=datetime(2023, 1, 1, 0, 0, tzinfo=TIMEZONE),
+ max=datetime(2023, 12, 31, 0, 0, tzinfo=TIMEZONE),
+ value=(
+ datetime(2023, 3, 1, 0, 0, tzinfo=TIMEZONE),
+ datetime(2023, 9, 30, 0, 0, tzinfo=TIMEZONE),
+ ),
+ time_format="%Y-%m-%d",
+ timezone="UTC",
+ drag_range=True,
+ ),
+ ui.output_text("value9"),
+ ),
+ # Datetime slider
+ ui.card(
+ ui.card_header("Datetime Slider"),
+ ui.input_slider(
+ "slider10",
+ "With time format",
+ min=datetime(2023, 1, 1, 0, 0, tzinfo=TIMEZONE),
+ max=datetime(2023, 12, 31, 23, 59, tzinfo=TIMEZONE),
+ value=datetime(2023, 6, 15, 12, 30, tzinfo=TIMEZONE),
+ time_format="%Y-%m-%d %H:%M",
+ timezone="UTC",
+ ),
+ ui.output_text("value10"),
+ ),
+ width="400px",
+ ),
+)
+
+
+def server(input, output, session):
+ @render.text
+ @reactive.event(input.slider1)
+ def value1():
+ return f"Value: {input.slider1()}"
+
+ @render.text
+ @reactive.event(input.slider2)
+ def value2():
+ return f"Value: {input.slider2()}"
+
+ @render.text
+ @reactive.event(input.slider3)
+ def value3():
+ return f"Value: {input.slider3()}"
+
+ @render.text
+ @reactive.event(input.slider4)
+ def value4():
+ return f"Value: {input.slider4()}"
+
+ @render.text
+ @reactive.event(input.slider5)
+ def value5():
+ return f"Value: {input.slider5()}"
+
+ @render.text
+ @reactive.event(input.slider6)
+ def value6():
+ return f"Value: {input.slider6()}"
+
+ @render.text
+ @reactive.event(input.slider7)
+ def value7():
+ return f"Value: {input.slider7()}"
+
+ @render.text
+ @reactive.event(input.slider9)
+ def value9():
+ return f"Value: {input.slider9()}"
+
+ @render.text
+ @reactive.event(input.slider10)
+ def value10():
+ return f"Value: {input.slider10()}"
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_slider/app-express.py b/tests/playwright/ai_generated_apps/input_slider/app-express.py
new file mode 100644
index 000000000..9bf518a0f
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_slider/app-express.py
@@ -0,0 +1,132 @@
+from datetime import datetime
+from zoneinfo import ZoneInfo
+
+from shiny.express import input, render, ui
+
+# Define a consistent timezone
+TIMEZONE = ZoneInfo("UTC")
+
+ui.page_opts(title="Slider Parameters Demo", full_width=True)
+
+with ui.layout_column_wrap(width="400px"):
+ # Numeric Slider - basic parameters
+ with ui.card():
+ ui.card_header("Basic Numeric Slider")
+ ui.input_slider("slider1", "Min, max, value", min=0, max=100, value=50)
+
+ @render.text
+ def value1():
+ return f"Value: {input.slider1()}"
+
+ # Numeric Slider with step
+ with ui.card():
+ ui.card_header("Step Parameter")
+ ui.input_slider("slider2", "Step size = 10", min=0, max=100, value=50, step=10)
+
+ @render.text
+ def value2():
+ return f"Value: {input.slider2()}"
+
+ # Range Slider
+ with ui.card():
+ ui.card_header("Range Slider")
+ ui.input_slider("slider3", "Select a range", min=0, max=100, value=(30, 70))
+
+ @render.text
+ def value3():
+ return f"Value: {input.slider3()}"
+
+ # Date Slider
+ with ui.card():
+ ui.card_header("Date Slider")
+ ui.input_slider(
+ "slider4",
+ "Select a date",
+ min=datetime(2023, 1, 1, 0, 0, tzinfo=TIMEZONE),
+ max=datetime(2023, 12, 31, 0, 0, tzinfo=TIMEZONE),
+ value=datetime(2023, 6, 15, 12, 30, tzinfo=TIMEZONE),
+ time_format="%Y-%m-%d",
+ timezone="UTC",
+ )
+
+ @render.text
+ def value4():
+ return f"Value: {input.slider4()}"
+
+ # Animated Slider
+ with ui.card():
+ ui.card_header("Animated Slider")
+ ui.input_slider(
+ "slider5", "With animation", min=0, max=100, value=50, animate=True
+ )
+
+ @render.text
+ def value5():
+ return f"Value: {input.slider5()}"
+
+ # Slider with custom formatting
+ with ui.card():
+ ui.card_header("Custom Formatting")
+ ui.input_slider(
+ "slider6",
+ "With prefix and suffix",
+ min=0,
+ max=100,
+ value=50,
+ pre="$",
+ post="%",
+ sep=",",
+ )
+
+ @render.text
+ def value6():
+ return f"Value: {input.slider6()}"
+
+ # Slider with ticks
+ with ui.card():
+ ui.card_header("Ticks Display")
+ ui.input_slider(
+ "slider7", "With tick marks", min=0, max=100, value=50, ticks=True
+ )
+
+ @render.text
+ def value7():
+ return f"Value: {input.slider7()}"
+
+ # Date Range Slider with drag_range
+ with ui.card():
+ ui.card_header("Date Range")
+ ui.input_slider(
+ "slider9",
+ "Draggable range",
+ min=datetime(2023, 1, 1, 0, 0, tzinfo=TIMEZONE),
+ max=datetime(2023, 12, 31, 0, 0, tzinfo=TIMEZONE),
+ value=(
+ datetime(2023, 3, 1, 0, 0, tzinfo=TIMEZONE),
+ datetime(2023, 9, 30, 0, 0, tzinfo=TIMEZONE),
+ ),
+ time_format="%Y-%m-%d",
+ timezone="UTC",
+ drag_range=True,
+ )
+
+ @render.text
+ def value9():
+ return f"Value: {input.slider9()}"
+
+ # Datetime slider
+ with ui.card():
+ ui.card_header("Datetime Slider")
+ ui.input_slider(
+ "slider10",
+ "With time format",
+ min=datetime(2023, 1, 1, 0, 0, tzinfo=TIMEZONE),
+ max=datetime(2023, 12, 31, 23, 59, tzinfo=TIMEZONE),
+ value=datetime(2023, 6, 15, 12, 30, tzinfo=TIMEZONE),
+ time_format="%Y-%m-%d %H:%M",
+ timezone="UTC",
+ )
+
+ @render.text
+ def value10():
+ return f"Value: {input.slider10()}"
diff --git a/tests/playwright/ai_generated_apps/input_slider/test_input_slider_core_express.py b/tests/playwright/ai_generated_apps/input_slider/test_input_slider_core_express.py
new file mode 100644
index 000000000..e39b1ef98
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_slider/test_input_slider_core_express.py
@@ -0,0 +1,102 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_slider_parameters(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test basic numeric slider
+ slider1 = controller.InputSlider(page, "slider1")
+ value1 = controller.OutputText(page, "value1")
+ value1.expect_value("Value: 50")
+ slider1.expect_label("Min, max, value")
+ slider1.expect_min("0")
+ slider1.expect_max("100")
+ slider1.expect_value("50")
+
+ # Test slider with step
+ slider2 = controller.InputSlider(page, "slider2")
+ value2 = controller.OutputText(page, "value2")
+ value2.expect_value("Value: 50")
+ slider2.expect_label("Step size = 10")
+ slider2.expect_min("0")
+ slider2.expect_max("100")
+ slider2.expect_value("50")
+ slider2.expect_step("10")
+
+ # Test range slider
+ slider3 = controller.InputSliderRange(page, "slider3")
+ value3 = controller.OutputText(page, "value3")
+ value3.expect_value("Value: (30, 70)")
+ slider3.expect_label("Select a range")
+ slider3.expect_min("0")
+ slider3.expect_max("100")
+ slider3.expect_value(("30", "70"))
+
+ # Test date slider
+ slider4 = controller.InputSlider(page, "slider4")
+ value4 = controller.OutputText(page, "value4")
+ value4.expect_value("Value: 2023-06-15 12:30:00")
+ slider4.expect_label("Select a date")
+ slider4.expect_min("1672531200000.0") # 2023-01-01
+ slider4.expect_max("1703980800000.0") # 2023-12-31
+ slider4.expect_value("2023-06-15")
+
+ # Test animated slider
+ slider5 = controller.InputSlider(page, "slider5")
+ value5 = controller.OutputText(page, "value5")
+ value5.expect_value("Value: 50")
+ slider5.expect_label("With animation")
+ slider5.expect_min("0")
+ slider5.expect_max("100")
+ slider5.expect_value("50")
+
+ # Test formatted slider
+ slider6 = controller.InputSlider(page, "slider6")
+ value6 = controller.OutputText(page, "value6")
+ value6.expect_value("Value: 50")
+ slider6.expect_label("With prefix and suffix")
+ slider6.expect_min("0")
+ slider6.expect_max("100")
+ slider6.expect_value("$50%")
+ slider6.expect_pre("$")
+ slider6.expect_post("%")
+ slider6.expect_sep(",")
+
+ # Test slider with ticks
+ slider7 = controller.InputSlider(page, "slider7")
+ value7 = controller.OutputText(page, "value7")
+ value7.expect_value("Value: 50")
+ slider7.expect_label("With tick marks")
+ slider7.expect_min("0")
+ slider7.expect_max("100")
+ slider7.expect_value("50")
+ slider7.expect_ticks("true")
+
+ # Test date range slider
+ slider9 = controller.InputSliderRange(page, "slider9")
+ value9 = controller.OutputText(page, "value9")
+ value9.expect_value(
+ "Value: (datetime.datetime(2023, 3, 1, 0, 0), datetime.datetime(2023, 9, 30, 0, 0))"
+ )
+ slider9.expect_label("Draggable range")
+ slider9.expect_min("1672531200000.0") # 2023-01-01
+ slider9.expect_max("1703980800000.0") # 2023-12-31
+ slider9.expect_value(("2023-03-01", "2023-09-30"))
+ slider9.expect_drag_range("true")
+
+ # Test datetime slider
+ slider10 = controller.InputSlider(page, "slider10")
+ value10 = controller.OutputText(page, "value10")
+ value10.expect_value("Value: 2023-06-15 12:30:00")
+ slider10.expect_label("With time format")
+ slider10.expect_min("1672531200000.0") # 2023-01-01 00:00
+ slider10.expect_max("1704067140000.0") # 2023-12-31 23:59
+ slider10.expect_value("2023-06-15 12:30")
+ slider10.expect_time_format("%Y-%m-%d %H:%M")
+ slider10.expect_timezone("UTC")
diff --git a/tests/playwright/ai_generated_apps/input_switch/app-core.py b/tests/playwright/ai_generated_apps/input_switch/app-core.py
new file mode 100644
index 000000000..03bdae2fc
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_switch/app-core.py
@@ -0,0 +1,35 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fluid(
+ # Create a card to contain the switches
+ ui.card(
+ ui.card_header("Switch Demo"),
+ # Basic switch with default parameters
+ ui.input_switch(
+ id="switch1", label="Basic switch (default params)", value=False
+ ),
+ # Switch with custom width
+ ui.input_switch(
+ id="switch2", label="Switch with custom width", value=True, width="300px"
+ ),
+ # Output for switch states
+ ui.output_text("switch_states"),
+ full_screen=True,
+ height="300px",
+ id="card1",
+ )
+)
+
+
+# Define the server
+def server(input, output, session):
+ @render.text
+ def switch_states():
+ return (
+ f"Switch 1 state: {input.switch1()}\n" f"Switch 2 state: {input.switch2()}"
+ )
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_switch/app-express.py b/tests/playwright/ai_generated_apps/input_switch/app-express.py
new file mode 100644
index 000000000..2bf875fc4
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_switch/app-express.py
@@ -0,0 +1,23 @@
+from shiny.express import input, render, ui
+
+# Set page title
+ui.page_opts(full_width=True)
+
+# Create a card to contain the switches
+with ui.card(full_screen=True, height="300px", id="card1"):
+ ui.card_header("Switch Demo")
+
+ # Basic switch with default parameters
+ ui.input_switch(id="switch1", label="Basic switch (default params)", value=False)
+
+ # Switch with custom width
+ ui.input_switch(
+ id="switch2", label="Switch with custom width", value=True, width="300px"
+ )
+
+ # Display switch states
+ @render.text
+ def switch_states():
+ return (
+ f"Switch 1 state: {input.switch1()}\n" f"Switch 2 state: {input.switch2()}"
+ )
diff --git a/tests/playwright/ai_generated_apps/input_switch/test_input_switch_core_express.py b/tests/playwright/ai_generated_apps/input_switch/test_input_switch_core_express.py
new file mode 100644
index 000000000..2c5ca3531
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_switch/test_input_switch_core_express.py
@@ -0,0 +1,36 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_switch_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test card1
+ card1 = controller.Card(page, "card1")
+ card1.expect_header("Switch Demo")
+ card1.expect_height("300px")
+ card1.expect_full_screen_available(True)
+
+ # Test switch1
+ switch1 = controller.InputSwitch(page, "switch1")
+ switch1.expect_label("Basic switch (default params)")
+ switch1.expect_checked(False) # Test initial value
+
+ # Toggle switch1 and verify
+ switch1.set(True)
+ switch1.expect_checked(True)
+
+ # Test switch2
+ switch2 = controller.InputSwitch(page, "switch2")
+ switch2.expect_label("Switch with custom width")
+ switch2.expect_checked(True) # Test initial value
+ switch2.expect_width("300px")
+
+ # Toggle switch2 and verify
+ switch2.set(False)
+ switch2.expect_checked(False)
diff --git a/tests/playwright/ai_generated_apps/input_task_button/app-core.py b/tests/playwright/ai_generated_apps/input_task_button/app-core.py
new file mode 100644
index 000000000..c99e646bb
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_task_button/app-core.py
@@ -0,0 +1,50 @@
+from shiny import App, reactive, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Add Font Awesome CSS in the head section
+ ui.tags.head(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css",
+ )
+ ),
+ # Create a card to contain our task button and status
+ ui.card(
+ ui.card_header("Task Button Demo"),
+ # Define the task button
+ ui.input_task_button(
+ id="task_btn",
+ label="Run Task",
+ icon=ui.tags.i(class_="fa-solid fa-play"),
+ label_busy="Processing...",
+ icon_busy=ui.tags.i(class_="fa-solid fa-spinner fa-spin"),
+ class_="btn-primary m-3",
+ ),
+ ui.output_text("task_status"),
+ height="300px",
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @output
+ @render.text
+ def task_status():
+ count = input.task_btn()
+ if count == 0:
+ return "Task hasn't started yet"
+ return f"Task has been run {count} times"
+
+ @reactive.effect
+ @reactive.event(input.task_btn)
+ def handle_task():
+ import time
+
+ # Simulate a long-running task
+ time.sleep(2)
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_task_button/app-express.py b/tests/playwright/ai_generated_apps/input_task_button/app-express.py
new file mode 100644
index 000000000..90f5c69d5
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_task_button/app-express.py
@@ -0,0 +1,43 @@
+from shiny import reactive
+from shiny.express import input, render, ui
+
+# Page options for basic styling
+ui.page_opts(fillable=True)
+
+# Add Font Awesome CSS in the head section
+ui.head_content(
+ ui.HTML(
+ ''
+ )
+)
+
+# Create a card to contain our task button and status
+with ui.card(height="300px"):
+ ui.card_header("Task Button Demo")
+
+ # Define the task button
+ ui.input_task_button(
+ id="task_btn",
+ label="Run Task",
+ icon=ui.tags.i(class_="fa-solid fa-play"),
+ label_busy="Processing...",
+ icon_busy=ui.tags.i(class_="fa-solid fa-spinner fa-spin"),
+ class_="btn-primary m-3",
+ )
+
+ @render.text
+ def task_status():
+ count = input.task_btn()
+ if count == 0:
+ return "Task hasn't started yet"
+ return f"Task has been run {count} times"
+
+
+# Effect to handle the task
+@reactive.effect
+@reactive.event(input.task_btn)
+def handle_task():
+ import time
+
+ # Simulate a long-running task
+ time.sleep(2)
diff --git a/tests/playwright/ai_generated_apps/input_task_button/test_input_task_button_core_express.py b/tests/playwright/ai_generated_apps/input_task_button/test_input_task_button_core_express.py
new file mode 100644
index 000000000..fee1076bc
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_task_button/test_input_task_button_core_express.py
@@ -0,0 +1,35 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_task_button_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Get the task button controller
+ task_btn = controller.InputTaskButton(page, "task_btn")
+ task_status = controller.OutputText(page, "task_status")
+
+ # Test initial state
+ task_btn.expect_label("Run Task")
+ task_btn.expect_label_busy("Processing...")
+ task_btn.expect_state("ready")
+ task_status.expect_value("Task hasn't started yet")
+
+ # Click the button and test busy state
+ task_btn.click()
+ task_btn.expect_state("busy")
+
+ # After task completes, verify new state and status
+ task_btn.expect_state("ready", timeout=3000) # Allow time for the 2-second sleep
+ task_status.expect_value("Task has been run 1 times")
+
+ # Test a second click
+ task_btn.click()
+ task_btn.expect_state("busy")
+ task_btn.expect_state("ready", timeout=3000)
+ task_status.expect_value("Task has been run 2 times")
diff --git a/tests/playwright/ai_generated_apps/input_text/app-core.py b/tests/playwright/ai_generated_apps/input_text/app-core.py
new file mode 100644
index 000000000..aa58431eb
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_text/app-core.py
@@ -0,0 +1,34 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Create a card to contain the input and output
+ ui.card(
+ ui.card_header("Text Input Example"),
+ # Create text input with all possible parameters
+ ui.input_text(
+ id="demo_text", # Required: unique identifier
+ label="Demo Text Input", # Required: label text
+ value="Initial value", # Optional: starting value
+ width="300px", # Optional: CSS width
+ placeholder="Enter text here", # Optional: hint text
+ autocomplete="off", # Optional: browser autocomplete behavior
+ spellcheck="true", # Optional: browser spellcheck behavior
+ ),
+ # Add some spacing
+ ui.hr(),
+ # Output area for the current value
+ ui.output_text("current_value"),
+ )
+)
+
+
+# Define the server function
+def server(input, output, session):
+ @render.text
+ def current_value():
+ return f"Current value: {input.demo_text()}"
+
+
+# Create and return the app object
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_text/app-express.py b/tests/playwright/ai_generated_apps/input_text/app-express.py
new file mode 100644
index 000000000..81b04b067
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_text/app-express.py
@@ -0,0 +1,27 @@
+from shiny.express import input, render, ui
+
+# Set page options for a clean layout
+ui.page_opts(fillable=True)
+
+# Create a card to contain the input and output
+with ui.card():
+ ui.card_header("Text Input Example")
+
+ # Create text input with all possible parameters
+ ui.input_text(
+ id="demo_text", # Required: unique identifier
+ label="Demo Text Input", # Required: label text
+ value="Initial value", # Optional: starting value
+ width="300px", # Optional: CSS width
+ placeholder="Enter text here", # Optional: hint text
+ autocomplete="off", # Optional: browser autocomplete behavior
+ spellcheck="true", # Optional: browser spellcheck behavior
+ )
+
+ # Add some spacing
+ ui.hr()
+
+ # Display area for the current value
+ @render.text
+ def current_value():
+ return f"Current value: {input.demo_text()}"
diff --git a/tests/playwright/ai_generated_apps/input_text/test_input_text_core_express.py b/tests/playwright/ai_generated_apps/input_text/test_input_text_core_express.py
new file mode 100644
index 000000000..a5d6eb0d9
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_text/test_input_text_core_express.py
@@ -0,0 +1,32 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_text_input_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test text input
+ text_input = controller.InputText(page, "demo_text")
+ text_output = controller.OutputText(page, "current_value")
+
+ # Test initial state
+ text_input.expect_label("Demo Text Input")
+ text_input.expect_value("Initial value")
+ text_input.expect_width("300px")
+ text_input.expect_placeholder("Enter text here")
+ text_input.expect_autocomplete("off")
+ text_input.expect_spellcheck("true")
+ text_output.expect_value("Current value: Initial value")
+
+ # Test setting new value
+ text_input.set("New test value")
+ text_output.expect_value("Current value: New test value")
+
+ # Test empty value
+ text_input.set("")
+ text_output.expect_value("Current value: ")
diff --git a/tests/playwright/ai_generated_apps/input_text_area/app-core.py b/tests/playwright/ai_generated_apps/input_text_area/app-core.py
new file mode 100644
index 000000000..bc190882b
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_text_area/app-core.py
@@ -0,0 +1,39 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fluid(
+ # Page title is set in the page_fluid
+ ui.tags.head(ui.tags.title("Text Area Demo")),
+ # Create a text area with all possible parameters
+ ui.input_text_area(
+ id="text_input",
+ label="Enter your text:",
+ value="This is some default text.\nIt has multiple lines.\nYou can edit it!",
+ width="500px",
+ height="200px",
+ cols=50,
+ rows=8,
+ placeholder="Type something here...",
+ resize="both",
+ autoresize=True,
+ spellcheck="true",
+ ),
+ # Add some spacing
+ ui.br(),
+ ui.br(),
+ # Add a header for the output
+ ui.h4("Output:"),
+ # Add the output text area
+ ui.output_text("show_text"),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @render.text
+ def show_text():
+ return f"You entered:\n{input.text_input()}"
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/input_text_area/app-express.py b/tests/playwright/ai_generated_apps/input_text_area/app-express.py
new file mode 100644
index 000000000..0f41661fd
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_text_area/app-express.py
@@ -0,0 +1,34 @@
+from shiny.express import input, render, ui
+
+ui.page_opts(full_width=True)
+
+ui.tags.head(
+ ui.tags.title("Text Area Demo"),
+)
+# Create a text area with all possible parameters
+ui.input_text_area(
+ id="text_input",
+ label="Enter your text:",
+ value="This is some default text.\nIt has multiple lines.\nYou can edit it!",
+ width="500px",
+ height="200px",
+ cols=50,
+ rows=8,
+ placeholder="Type something here...",
+ resize="both",
+ autoresize=True,
+ spellcheck="true",
+)
+
+# Add some spacing
+ui.br()
+ui.br()
+
+# Add a header for the output
+ui.h4("Output:")
+
+
+# Display the input value in a pre-formatted text block
+@render.text
+def show_text():
+ return f"You entered:\n{input.text_input()}"
diff --git a/tests/playwright/ai_generated_apps/input_text_area/test_input_text_area_core_express.py b/tests/playwright/ai_generated_apps/input_text_area/test_input_text_area_core_express.py
new file mode 100644
index 000000000..00839927d
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/input_text_area/test_input_text_area_core_express.py
@@ -0,0 +1,39 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_text_area_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Get the text area controller
+ text_area = controller.InputTextArea(page, "text_input")
+
+ # Test initial state
+ text_area.expect_label("Enter your text:")
+ text_area.expect_value(
+ "This is some default text.\nIt has multiple lines.\nYou can edit it!"
+ )
+ text_area.expect_width("500px")
+ text_area.expect_height("194px") # 200px - padding
+ text_area.expect_cols("50")
+ text_area.expect_rows("8")
+ text_area.expect_placeholder("Type something here...")
+ text_area.expect_resize("both")
+ text_area.expect_autoresize(True)
+ text_area.expect_spellcheck("true")
+
+ # Test output text controller
+ output_text = controller.OutputText(page, "show_text")
+ output_text.expect_value(
+ "You entered:\nThis is some default text.\nIt has multiple lines.\nYou can edit it!"
+ )
+
+ # Test setting new value
+ new_text = "This is new text\nWith new lines"
+ text_area.set(new_text)
+ output_text.expect_value(f"You entered:\n{new_text}")
diff --git a/tests/playwright/ai_generated_apps/navset_pill/app-core.py b/tests/playwright/ai_generated_apps/navset_pill/app-core.py
new file mode 100644
index 000000000..8da59279e
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/navset_pill/app-core.py
@@ -0,0 +1,55 @@
+from shiny import App, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Create a navset_pill with all possible parameters
+ ui.navset_pill(
+ # Panel A
+ ui.nav_panel(
+ "A",
+ "This is content for Panel A",
+ ui.input_slider("n1", "N1", min=0, max=100, value=20),
+ ui.output_text("panel_a_text"),
+ ),
+ # Panel B
+ ui.nav_panel(
+ "B",
+ "This is content for Panel B",
+ ui.input_numeric("n2", "N2", value=10),
+ ui.output_text("panel_b_text"),
+ ),
+ # Panel C
+ ui.nav_panel(
+ "C",
+ "This is content for Panel C",
+ ui.input_text("txt", "Enter text", "Hello"),
+ ui.output_text("panel_c_text"),
+ ),
+ id="pills",
+ ),
+ # Show which panel is currently selected
+ ui.output_text("selected_panel"),
+)
+
+
+# Define the server
+def server(input, output, session):
+ @render.text
+ def panel_a_text():
+ return f"Value of n1: {input.n1()}"
+
+ @render.text
+ def panel_b_text():
+ return f"Value of n2: {input.n2()}"
+
+ @render.text
+ def panel_c_text():
+ return f"You entered: {input.txt()}"
+
+ @render.text
+ def selected_panel():
+ return f"Currently selected panel: {input.pills()}"
+
+
+# Create the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/navset_pill/app-express.py b/tests/playwright/ai_generated_apps/navset_pill/app-express.py
new file mode 100644
index 000000000..8ef6d6796
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/navset_pill/app-express.py
@@ -0,0 +1,39 @@
+from shiny.express import input, render, ui
+
+# Page options for the app
+ui.page_opts(fillable=True)
+
+# Create a navset_pill with all possible parameters
+with ui.navset_pill(id="pills"):
+ # Panel A
+ with ui.nav_panel("A"):
+ "This is content for Panel A"
+ ui.input_slider("n1", "N1", min=0, max=100, value=20)
+
+ @render.text
+ def panel_a_text():
+ return f"Value of n1: {input.n1()}"
+
+ # Panel B
+ with ui.nav_panel("B"):
+ "This is content for Panel B"
+ ui.input_numeric("n2", "N2", value=10)
+
+ @render.text
+ def panel_b_text():
+ return f"Value of n2: {input.n2()}"
+
+ # Panel C
+ with ui.nav_panel("C"):
+ "This is content for Panel C"
+ ui.input_text("txt", "Enter text", "Hello")
+
+ @render.text
+ def panel_c_text():
+ return f"You entered: {input.txt()}"
+
+
+# Show which panel is currently selected
+@render.text
+def selected_panel():
+ return f"Currently selected panel: {input.pills()}"
diff --git a/tests/playwright/ai_generated_apps/navset_pill/test_navset_pill_core_express.py b/tests/playwright/ai_generated_apps/navset_pill/test_navset_pill_core_express.py
new file mode 100644
index 000000000..d080ca743
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/navset_pill/test_navset_pill_core_express.py
@@ -0,0 +1,67 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_navset_pill_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test navset pill
+ navset = controller.NavsetPill(page, "pills")
+ selected_panel_text = controller.OutputText(page, "selected_panel")
+
+ # Test initial state
+ navset.expect_nav_titles(["A", "B", "C"])
+ navset.expect_nav_values(["A", "B", "C"])
+ navset.expect_value("A") # First panel should be selected by default
+ selected_panel_text.expect_value("Currently selected panel: A")
+
+ # Test Panel A components
+ slider = controller.InputSlider(page, "n1")
+ panel_a_text = controller.OutputText(page, "panel_a_text")
+
+ slider.expect_label("N1")
+ slider.expect_min("0")
+ slider.expect_max("100")
+ slider.expect_value("20")
+ panel_a_text.expect_value("Value of n1: 20")
+
+ # Change slider value and verify
+ slider.set("50")
+ panel_a_text.expect_value("Value of n1: 50")
+
+ # Switch to Panel B and test its components
+ navset.set("B")
+ navset.expect_value("B")
+ selected_panel_text.expect_value("Currently selected panel: B")
+
+ numeric = controller.InputNumeric(page, "n2")
+ panel_b_text = controller.OutputText(page, "panel_b_text")
+
+ numeric.expect_label("N2")
+ numeric.expect_value("10")
+ panel_b_text.expect_value("Value of n2: 10")
+
+ # Change numeric value and verify
+ numeric.set("25")
+ panel_b_text.expect_value("Value of n2: 25")
+
+ # Switch to Panel C and test its components
+ navset.set("C")
+ navset.expect_value("C")
+ selected_panel_text.expect_value("Currently selected panel: C")
+
+ text_input = controller.InputText(page, "txt")
+ panel_c_text = controller.OutputText(page, "panel_c_text")
+
+ text_input.expect_label("Enter text")
+ text_input.expect_value("Hello")
+ panel_c_text.expect_value("You entered: Hello")
+
+ # Change text value and verify
+ text_input.set("Testing")
+ panel_c_text.expect_value("You entered: Testing")
diff --git a/tests/playwright/ai_generated_apps/update_text/app-core.py b/tests/playwright/ai_generated_apps/update_text/app-core.py
new file mode 100644
index 000000000..0a2ce7ffe
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_text/app-core.py
@@ -0,0 +1,84 @@
+from shiny import App, reactive, render, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Layout with two columns
+ ui.layout_column_wrap(
+ # First card with text input
+ ui.card(
+ ui.card_header("Text Input Demo"),
+ ui.input_text(
+ "txt",
+ "Original Text",
+ value="Initial value",
+ placeholder="Type something...",
+ ),
+ ui.output_text("current_value"),
+ ),
+ # Second card with control buttons
+ ui.card(
+ ui.card_header("Control Buttons"),
+ ui.layout_column_wrap(
+ ui.input_action_button(
+ "update_all", "Update All Parameters", class_="btn-primary mb-3"
+ ),
+ ui.input_action_button(
+ "update_label", "Update Label Only", class_="btn-secondary mb-3"
+ ),
+ ui.input_action_button(
+ "update_value", "Update Value Only", class_="btn-success mb-3"
+ ),
+ ui.input_action_button(
+ "update_placeholder",
+ "Update Placeholder Only",
+ class_="btn-info mb-3",
+ ),
+ ),
+ ),
+ width=1 / 2,
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ # Render current value text
+ @render.text
+ def current_value():
+ return f"Current value: {input.txt()}"
+
+ # Effect for updating all parameters
+ @reactive.effect
+ @reactive.event(input.update_all)
+ def _():
+ ui.update_text(
+ id="txt",
+ label="Updated Label",
+ value="Updated Value",
+ placeholder="Updated Placeholder",
+ )
+
+ # Effect for updating label only
+ @reactive.effect
+ @reactive.event(input.update_label)
+ def _():
+ ui.update_text(id="txt", label=f"Label Updated {input.update_label()} times")
+
+ # Effect for updating value only
+ @reactive.effect
+ @reactive.event(input.update_value)
+ def _():
+ ui.update_text(id="txt", value=f"Value Updated {input.update_value()} times")
+
+ # Effect for updating placeholder only
+ @reactive.effect
+ @reactive.event(input.update_placeholder)
+ def _():
+ ui.update_text(
+ id="txt",
+ placeholder=f"Placeholder Updated {input.update_placeholder()} times",
+ )
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/update_text/app-express.py b/tests/playwright/ai_generated_apps/update_text/app-express.py
new file mode 100644
index 000000000..7e2e6eb01
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_text/app-express.py
@@ -0,0 +1,69 @@
+from shiny import reactive
+from shiny.express import input, render, ui
+
+# Page title
+ui.page_opts(fillable=True)
+
+with ui.layout_column_wrap(width=1 / 2):
+ # Input text that will be updated
+ with ui.card():
+ ui.card_header("Text Input Demo")
+ ui.input_text(
+ "txt",
+ "Original Text",
+ value="Initial value",
+ placeholder="Type something...",
+ )
+
+ @render.text
+ def current_value():
+ return f"Current value: {input.txt()}"
+
+ # Buttons to trigger different update scenarios
+ with ui.card():
+ ui.card_header("Control Buttons")
+ with ui.layout_column_wrap():
+ ui.input_action_button(
+ "update_all", "Update All Parameters", class_="btn-primary mb-3"
+ )
+ ui.input_action_button(
+ "update_label", "Update Label Only", class_="btn-secondary mb-3"
+ )
+ ui.input_action_button(
+ "update_value", "Update Value Only", class_="btn-success mb-3"
+ )
+ ui.input_action_button(
+ "update_placeholder", "Update Placeholder Only", class_="btn-info mb-3"
+ )
+
+
+# Effects to handle different update scenarios
+@reactive.effect
+@reactive.event(input.update_all)
+def _():
+ ui.update_text(
+ id="txt",
+ label="Updated Label",
+ value="Updated Value",
+ placeholder="Updated Placeholder",
+ )
+
+
+@reactive.effect
+@reactive.event(input.update_label)
+def _():
+ ui.update_text(id="txt", label=f"Label Updated {input.update_label()} times")
+
+
+@reactive.effect
+@reactive.event(input.update_value)
+def _():
+ ui.update_text(id="txt", value=f"Value Updated {input.update_value()} times")
+
+
+@reactive.effect
+@reactive.event(input.update_placeholder)
+def _():
+ ui.update_text(
+ id="txt", placeholder=f"Placeholder Updated {input.update_placeholder()} times"
+ )
diff --git a/tests/playwright/ai_generated_apps/update_text/test_update_text_core_express.py b/tests/playwright/ai_generated_apps/update_text/test_update_text_core_express.py
new file mode 100644
index 000000000..9b6d1526e
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_text/test_update_text_core_express.py
@@ -0,0 +1,64 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_update_text(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Get controllers for the components with IDs
+ text_input = controller.InputText(page, "txt")
+ text_output = controller.OutputText(page, "current_value")
+ update_all_btn = controller.InputActionButton(page, "update_all")
+ update_label_btn = controller.InputActionButton(page, "update_label")
+ update_value_btn = controller.InputActionButton(page, "update_value")
+ update_placeholder_btn = controller.InputActionButton(page, "update_placeholder")
+
+ # Test initial state
+ text_input.expect_label("Original Text")
+ text_input.expect_value("Initial value")
+ text_input.expect_placeholder("Type something...")
+ text_output.expect_value("Current value: Initial value")
+
+ # Test update all parameters
+ update_all_btn.click()
+ text_input.expect_label("Updated Label")
+ text_input.expect_value("Updated Value")
+ text_input.expect_placeholder("Updated Placeholder")
+ text_output.expect_value("Current value: Updated Value")
+
+ # Test update label only
+ update_label_btn.click()
+ text_input.expect_label("Label Updated 1 times")
+ text_output.expect_value(
+ "Current value: Updated Value"
+ ) # Value should remain unchanged
+
+ # Test update value only
+ update_value_btn.click()
+ text_input.expect_value("Value Updated 1 times")
+ text_output.expect_value("Current value: Value Updated 1 times")
+ text_input.expect_label("Label Updated 1 times") # Label should remain unchanged
+
+ # Test update placeholder only
+ update_placeholder_btn.click()
+ text_input.expect_placeholder("Placeholder Updated 1 times")
+ text_output.expect_value(
+ "Current value: Value Updated 1 times"
+ ) # Value should remain unchanged
+
+ # Test manual text input
+ text_input.set("User typed value")
+ text_output.expect_value("Current value: User typed value")
+
+ # Test multiple updates
+ update_label_btn.click()
+ text_input.expect_label("Label Updated 2 times")
+ update_value_btn.click()
+ text_input.expect_value("Value Updated 2 times")
+ update_placeholder_btn.click()
+ text_input.expect_placeholder("Placeholder Updated 2 times")
diff --git a/tests/playwright/ai_generated_apps/update_text_area/app-core.py b/tests/playwright/ai_generated_apps/update_text_area/app-core.py
new file mode 100644
index 000000000..edecea96d
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_text_area/app-core.py
@@ -0,0 +1,59 @@
+from shiny import App, reactive, render, ui
+
+app_ui = ui.page_fillable(
+ # Set page title in the UI
+ ui.panel_title("Text Area Update Demo"),
+ # Initial text area with a card wrapper
+ ui.card(
+ ui.card_header("Text Area Demo"),
+ ui.input_text_area(
+ id="textarea",
+ label="Sample Text Area",
+ value="Initial text",
+ placeholder="Enter your text here",
+ rows=5,
+ height="200px",
+ ),
+ ),
+ # Controls for updating text area in a separate card
+ ui.card(
+ ui.card_header("Control Panel"),
+ ui.layout_column_wrap(
+ ui.input_text("new_label", "New Label", value="Updated Label"),
+ ui.input_text("new_value", "New Value", value="Updated text content"),
+ ui.input_text(
+ "new_placeholder", "New Placeholder", value="Updated placeholder text"
+ ),
+ ui.input_action_button("update", "Update Text Area", class_="btn-primary"),
+ width=1 / 2,
+ ),
+ ),
+ # Display current values for verification
+ ui.card(ui.card_header("Current Values"), ui.output_text("show_values")),
+ fillable_mobile=True,
+)
+
+
+def server(input, output, session):
+ # Display current values
+ @render.text
+ def show_values():
+ return f"""
+ Current Label: {input.new_label()}
+ Current Value: {input.new_value()}
+ Current Placeholder: {input.new_placeholder()}
+ """
+
+ # Effect to update the text area when the button is clicked
+ @reactive.effect
+ @reactive.event(input.update)
+ def _():
+ ui.update_text_area(
+ id="textarea",
+ label=input.new_label(),
+ value=input.new_value(),
+ placeholder=input.new_placeholder(),
+ )
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/update_text_area/app-express.py b/tests/playwright/ai_generated_apps/update_text_area/app-express.py
new file mode 100644
index 000000000..4db5c2375
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_text_area/app-express.py
@@ -0,0 +1,51 @@
+from shiny import reactive
+from shiny.express import input, render, ui
+
+ui.page_opts(title="Text Area Update Demo", fillable=True)
+
+# Initial text area with a card wrapper for better visual organization
+with ui.card():
+ ui.card_header("Text Area Demo")
+ ui.input_text_area(
+ id="textarea",
+ label="Sample Text Area",
+ value="Initial text",
+ placeholder="Enter your text here",
+ rows=5,
+ height="200px",
+ )
+
+# Controls for updating text area in a separate card
+with ui.card():
+ ui.card_header("Control Panel")
+ with ui.layout_column_wrap(width=1 / 2):
+ ui.input_text("new_label", "New Label", value="Updated Label")
+ ui.input_text("new_value", "New Value", value="Updated text content")
+ ui.input_text(
+ "new_placeholder", "New Placeholder", value="Updated placeholder text"
+ )
+ ui.input_action_button("update", "Update Text Area", class_="btn-primary")
+
+# Display current values for verification
+with ui.card():
+ ui.card_header("Current Values")
+
+ @render.text
+ def show_values():
+ return f"""
+ Current Label: {input.new_label()}
+ Current Value: {input.new_value()}
+ Current Placeholder: {input.new_placeholder()}
+ """
+
+
+# Effect to update the text area when the button is clicked
+@reactive.effect
+@reactive.event(input.update)
+def _():
+ ui.update_text_area(
+ id="textarea",
+ label=input.new_label(),
+ value=input.new_value(),
+ placeholder=input.new_placeholder(),
+ )
diff --git a/tests/playwright/ai_generated_apps/update_text_area/test_update_text_area_core_express.py b/tests/playwright/ai_generated_apps/update_text_area/test_update_text_area_core_express.py
new file mode 100644
index 000000000..292d6d37c
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_text_area/test_update_text_area_core_express.py
@@ -0,0 +1,65 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_update_text_area(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Get the text area controller
+ text_area = controller.InputTextArea(page, "textarea")
+
+ new_label = controller.InputText(page, "new_label")
+ new_value = controller.InputText(page, "new_value")
+ new_placeholder = controller.InputText(page, "new_placeholder")
+
+ show_values = controller.OutputText(page, "show_values")
+ show_values.expect_value(
+ """
+ Current Label: Updated Label
+ Current Value: Updated text content
+ Current Placeholder: Updated placeholder text
+ """
+ )
+
+ # Update text are button
+ update_button = controller.InputActionButton(page, "update")
+
+ # Test initial state
+ text_area.expect_label("Sample Text Area")
+ text_area.expect_value("Initial text")
+ text_area.expect_placeholder("Enter your text here")
+
+ new_label.expect_label("New Label")
+ new_label.expect_value("Updated Label")
+
+ new_placeholder.expect_label("New Placeholder")
+ new_placeholder.expect_value("Updated placeholder text")
+
+ new_value.expect_label("New Value")
+ new_value.expect_value("Updated text content")
+
+ # Update the text area
+ new_label.set("New Label")
+ new_value.set("New text content")
+ new_placeholder.set("New placeholder text")
+ update_button.click()
+ text_area.expect_label("New Label")
+ text_area.expect_value("New text content")
+ text_area.expect_placeholder("New placeholder text")
+
+ # Check the output
+ show_values.expect_value(
+ """
+ Current Label: New Label
+ Current Value: New text content
+ Current Placeholder: New placeholder text
+ """
+ )
+ text_area.expect_label("New Label")
+ text_area.expect_value("New text content")
+ text_area.expect_placeholder("New placeholder text")
diff --git a/tests/playwright/ai_generated_apps/update_tooltip/app-core.py b/tests/playwright/ai_generated_apps/update_tooltip/app-core.py
new file mode 100644
index 000000000..3f2eece04
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_tooltip/app-core.py
@@ -0,0 +1,92 @@
+from shiny import App, reactive, ui
+
+# Define the UI
+app_ui = ui.page_fillable(
+ # Add Font Awesome for icons
+ ui.tags.head(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css",
+ ),
+ ),
+ # Create a container card
+ ui.card(
+ ui.card_header("Tooltip Demo"),
+ # Control buttons in a row
+ ui.layout_column_wrap(
+ ui.input_action_button(
+ "btn_show",
+ "Show tooltip",
+ class_="btn btn-info",
+ icon=ui.tags.i(class_="fa-solid fa-eye"),
+ ),
+ ui.input_action_button(
+ "btn_close",
+ "Close tooltip",
+ class_="btn btn-warning",
+ icon=ui.tags.i(class_="fa-solid fa-eye-slash"),
+ ),
+ ui.input_action_button(
+ "btn_update",
+ "Update tooltip",
+ class_="btn btn-success",
+ icon=ui.tags.i(class_="fa-solid fa-sync"),
+ ),
+ width=1 / 3,
+ ),
+ ui.hr(),
+ # Center the tooltip button
+ ui.div(
+ ui.tooltip(
+ ui.input_action_button(
+ "btn_w_tooltip",
+ "Hover over me!",
+ class_="btn btn-primary btn-lg",
+ icon=ui.tags.i(class_="fa-solid fa-info-circle"),
+ ),
+ "Initial tooltip message - try the buttons above!",
+ id="tooltip_id",
+ placement="right",
+ ),
+ class_="d-flex justify-content-center align-items-center",
+ style="height: 200px;",
+ ),
+ ),
+)
+
+
+# Define the server
+def server(input, output, session):
+ # Effect to show tooltip
+ @reactive.effect
+ @reactive.event(input.btn_show)
+ def _():
+ ui.update_tooltip("tooltip_id", show=True)
+
+ # Effect to close tooltip
+ @reactive.effect
+ @reactive.event(input.btn_close)
+ def _():
+ ui.update_tooltip("tooltip_id", show=False)
+
+ # Effect to update tooltip content and show it
+ @reactive.effect
+ @reactive.event(input.btn_update)
+ def _():
+ # Create dynamic content based on number of clicks
+ count = input.btn_update()
+ content = f"Tooltip updated {count} time{'s' if count > 1 else ''}!"
+ # Update tooltip with new content and show it
+ ui.update_tooltip("tooltip_id", content, show=True)
+
+ # Show notification when button with tooltip is clicked
+ @reactive.effect
+ @reactive.event(input.btn_w_tooltip)
+ def _():
+ ui.notification_show(
+ "Button clicked!", duration=2, type="message", close_button=True
+ )
+
+
+# Create and return the app
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/update_tooltip/app-express.py b/tests/playwright/ai_generated_apps/update_tooltip/app-express.py
new file mode 100644
index 000000000..73a55f3e5
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_tooltip/app-express.py
@@ -0,0 +1,95 @@
+from shiny import reactive
+from shiny.express import input, ui
+
+# Page options
+ui.page_opts(
+ title="Update Tooltip Demo",
+ fillable=True,
+ # Add Font Awesome for icons
+)
+
+# Add Font Awesome CSS in the head section first
+ui.head_content(
+ ui.tags.link(
+ rel="stylesheet",
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css",
+ )
+)
+
+# Create a container card for better organization
+with ui.card():
+ ui.card_header("Tooltip Demo")
+
+ # Control buttons in a row
+ with ui.layout_column_wrap(width=1 / 3):
+ ui.input_action_button(
+ "btn_show",
+ "Show tooltip",
+ class_="btn btn-info",
+ icon=ui.tags.i(class_="fa-solid fa-eye"),
+ )
+
+ ui.input_action_button(
+ "btn_close",
+ "Close tooltip",
+ class_="btn btn-warning",
+ icon=ui.tags.i(class_="fa-solid fa-eye-slash"),
+ )
+
+ ui.input_action_button(
+ "btn_update",
+ "Update tooltip",
+ class_="btn btn-success",
+ icon=ui.tags.i(class_="fa-solid fa-sync"),
+ )
+
+ # Spacer
+ ui.hr()
+
+ # Center the tooltip button
+ with ui.div(
+ class_="d-flex justify-content-center align-items-center",
+ style="height: 200px;",
+ ):
+ with ui.tooltip(id="tooltip_id", placement="right"):
+ ui.input_action_button(
+ "btn_w_tooltip",
+ "Hover over me!",
+ class_="btn btn-primary btn-lg",
+ icon=ui.tags.i(class_="fa-solid fa-info-circle"),
+ )
+ "Initial tooltip message - try the buttons above!"
+
+
+# Effect to show tooltip
+@reactive.effect
+@reactive.event(input.btn_show)
+def _():
+ ui.update_tooltip("tooltip_id", show=True)
+
+
+# Effect to close tooltip
+@reactive.effect
+@reactive.event(input.btn_close)
+def _():
+ ui.update_tooltip("tooltip_id", show=False)
+
+
+# Effect to update tooltip content and show it
+@reactive.effect
+@reactive.event(input.btn_update)
+def _():
+ # Create dynamic content based on number of clicks
+ count = input.btn_update()
+ content = f"Tooltip updated {count} time{'s' if count > 1 else ''}!"
+ # Update tooltip with new content and show it
+ ui.update_tooltip("tooltip_id", content, show=True)
+
+
+# Show notification when button with tooltip is clicked
+@reactive.effect
+@reactive.event(input.btn_w_tooltip)
+def _():
+ ui.notification_show(
+ "Button clicked!", duration=2, type="message", close_button=True
+ )
diff --git a/tests/playwright/ai_generated_apps/update_tooltip/test_update_tooltip_core_express.py b/tests/playwright/ai_generated_apps/update_tooltip/test_update_tooltip_core_express.py
new file mode 100644
index 000000000..1202a7e2b
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/update_tooltip/test_update_tooltip_core_express.py
@@ -0,0 +1,52 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_tooltip_demo(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Get tooltip controller
+ tooltip = controller.Tooltip(page, "tooltip_id")
+
+ # Get button controllers
+ show_btn = controller.InputActionButton(page, "btn_show")
+ close_btn = controller.InputActionButton(page, "btn_close")
+ update_btn = controller.InputActionButton(page, "btn_update")
+ tooltip_btn = controller.InputActionButton(page, "btn_w_tooltip")
+
+ # Test initial button labels
+ show_btn.expect_label("Show tooltip")
+ close_btn.expect_label("Close tooltip")
+ update_btn.expect_label("Update tooltip")
+ tooltip_btn.expect_label("Hover over me!")
+
+ # Test initial tooltip state and content
+ tooltip.expect_active(False)
+ show_btn.click()
+ tooltip.expect_body("Initial tooltip message - try the buttons above!")
+ tooltip.expect_placement("right")
+
+ # Test showing tooltip
+ tooltip.expect_active(True)
+
+ # Test closing tooltip
+ close_btn.click()
+ tooltip.expect_active(False)
+
+ # Test updating tooltip content
+ update_btn.click()
+ tooltip.expect_active(True)
+ tooltip.expect_body("Tooltip updated 1 time!")
+
+ close_btn.click()
+ tooltip.expect_active(False)
+
+ # Click update button again and verify content changes
+ update_btn.click()
+ tooltip.expect_active(True)
+ tooltip.expect_body("Tooltip updated 2 times!")
diff --git a/tests/playwright/ai_generated_apps/value_box/app-core.py b/tests/playwright/ai_generated_apps/value_box/app-core.py
new file mode 100644
index 000000000..6cc4ce28c
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/value_box/app-core.py
@@ -0,0 +1,120 @@
+import pandas as pd
+import plotly.graph_objects as go
+import shinywidgets as sw
+
+from shiny import App, ui
+
+# Create a custom icon for the showcase using Font Awesome
+chart_icon = ui.tags.i(class_="fa-solid fa-chart-simple", style="font-size: 8rem;")
+chart_thumbs_up_icon = ui.tags.i(
+ class_="fa-solid fa-thumbs-up", style="font-size: 5rem;"
+)
+chart_star_icon = ui.tags.i(class_="fa-solid fa-star", style="font-size: 5rem;")
+chart_heart_icon = ui.tags.i(class_="fa-solid fa-heart", style="font-size: 5rem;")
+chart_lightbulb_icon = ui.tags.i(
+ class_="fa-solid fa-lightbulb", style="font-size: 10rem;"
+)
+
+data = pd.DataFrame(
+ {"Year": range(2018, 2024), "Revenue": [100, 120, 110, 122, 118, 130]}
+)
+
+app_ui = ui.page_fluid(
+ # Add Font Awesome CSS to the app
+ ui.head_content(
+ ui.HTML(
+ ''
+ )
+ ),
+ ui.br(),
+ ui.layout_column_wrap(
+ # 1. Basic value box with left-center showcase layout (default)
+ ui.value_box(
+ "Revenue",
+ "$5.2M",
+ "Up 12% from last month",
+ id="left_center_value_box",
+ showcase=chart_icon,
+ theme="primary",
+ ),
+ # 2. Value box with top-right showcase layout
+ ui.value_box(
+ "Active Users",
+ "2.4K",
+ "Daily active users",
+ id="top_right_value_box",
+ showcase=chart_thumbs_up_icon,
+ showcase_layout="top right",
+ theme="bg-gradient-purple-red",
+ height="200px",
+ ),
+ # 3. Value box with bottom showcase layout
+ ui.value_box(
+ "Conversion Rate",
+ "3.8%",
+ "Increased by 0.5%",
+ id="bottom_value_box",
+ showcase=sw.output_widget("graph"),
+ showcase_layout="bottom",
+ theme="text-success",
+ height="200px",
+ ),
+ # 4. Value box with full screen capability and custom theme
+ ui.value_box(
+ "Total Sales",
+ "8,742",
+ "Year to date performance",
+ id="full_screen_value_box",
+ showcase=chart_lightbulb_icon,
+ full_screen=True,
+ theme="bg-gradient-orange-red",
+ height="600px",
+ min_height="150px",
+ max_height="300px",
+ fill=True,
+ ),
+ # 5. Value box with custom background color using class_
+ ui.value_box(
+ "Pending Orders",
+ "156",
+ "Requires attention",
+ id="custom_bg_value_box",
+ showcase=chart_heart_icon,
+ theme=None,
+ height="200px",
+ class_="bg-warning text-dark",
+ ),
+ width="400px",
+ ),
+)
+
+
+def server(input, output, session):
+ @sw.render_plotly
+ def graph():
+ fig = go.Figure()
+
+ # Add line trace
+ fig.add_trace(
+ go.Scatter(
+ x=data["Year"],
+ y=data["Revenue"],
+ mode="lines+markers",
+ line=dict(color="blue", width=2),
+ marker=dict(size=4),
+ )
+ )
+
+ # Update layout for sparkline appearance
+ fig.update_layout(
+ showlegend=False,
+ paper_bgcolor="mistyrose",
+ plot_bgcolor="rgba(0,0,0,0)",
+ xaxis=dict(showgrid=False, zeroline=False),
+ yaxis=dict(showgrid=False, zeroline=False),
+ )
+
+ return fig
+
+
+app = App(app_ui, server)
diff --git a/tests/playwright/ai_generated_apps/value_box/app-express.py b/tests/playwright/ai_generated_apps/value_box/app-express.py
new file mode 100644
index 000000000..5f26ed9ca
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/value_box/app-express.py
@@ -0,0 +1,115 @@
+import pandas as pd
+import plotly.graph_objects as go
+import shinywidgets as sw
+
+from shiny.express import ui
+
+ui.page_opts(full_width=True)
+
+# Create a custom icon for the showcase using Font Awesome
+chart_icon = ui.tags.i(class_="fa-solid fa-chart-simple", style="font-size: 8rem;")
+chart_thumbs_up_icon = ui.tags.i(
+ class_="fa-solid fa-thumbs-up", style="font-size: 5rem;"
+)
+chart_star_icon = ui.tags.i(class_="fa-solid fa-star", style="font-size: 5rem;")
+chart_heart_icon = ui.tags.i(class_="fa-solid fa-heart", style="font-size: 5rem;")
+chart_lightbulb_icon = ui.tags.i(
+ class_="fa-solid fa-lightbulb", style="font-size: 10rem;"
+)
+
+data = pd.DataFrame(
+ {"Year": range(2018, 2024), "Revenue": [100, 120, 110, 122, 118, 130]}
+)
+
+# Add Font Awesome CSS to the app
+ui.head_content(
+ ui.HTML(
+ ''
+ )
+)
+ui.br()
+
+with ui.layout_column_wrap(width="400px"):
+ # 1. Basic value box with left-center showcase layout (default)
+ with ui.value_box(id="left_center_value_box", showcase=chart_icon, theme="primary"):
+ "Revenue"
+ "$5.2M"
+ "Up 12% from last month"
+
+ # 2. Value box with top-right showcase layout
+ with ui.value_box(
+ id="top_right_value_box",
+ showcase=chart_thumbs_up_icon,
+ showcase_layout="top right",
+ theme="bg-gradient-purple-red",
+ height="200px",
+ ):
+ "Active Users"
+ "2.4K"
+ "Daily active users"
+
+ # 3. Value box with bottom showcase layout
+ with ui.value_box(
+ id="bottom_value_box",
+ showcase=sw.output_widget("graph"),
+ showcase_layout="bottom",
+ theme="text-success",
+ height="200px",
+ ):
+ "Conversion Rate"
+ "3.8%"
+ "Increased by 0.5%"
+ with ui.hold():
+
+ @sw.render_plotly
+ def graph():
+ fig = go.Figure()
+
+ # Add line trace
+ fig.add_trace(
+ go.Scatter(
+ x=data["Year"],
+ y=data["Revenue"],
+ mode="lines+markers",
+ line=dict(color="blue", width=2),
+ marker=dict(size=4),
+ )
+ )
+
+ # Update layout for sparkline appearance
+ fig.update_layout(
+ showlegend=False,
+ paper_bgcolor="mistyrose",
+ plot_bgcolor="rgba(0,0,0,0)",
+ xaxis=dict(showgrid=False, zeroline=False),
+ yaxis=dict(showgrid=False, zeroline=False),
+ )
+
+ return fig
+
+ # 4. Value box with full screen capability and custom theme
+ with ui.value_box(
+ id="full_screen_value_box",
+ showcase=chart_lightbulb_icon,
+ full_screen=True,
+ theme="bg-gradient-orange-red",
+ height="600px",
+ min_height="150px",
+ max_height="300px",
+ fill=True,
+ ):
+ "Total Sales"
+ "8,742"
+ "Year to date performance"
+
+ # 5. Value box with custom background color using class_
+ with ui.value_box(
+ id="custom_bg_value_box",
+ showcase=chart_heart_icon,
+ theme=None,
+ height="200px",
+ class_="bg-warning text-dark",
+ ):
+ "Pending Orders"
+ "156"
+ "Requires attention"
diff --git a/tests/playwright/ai_generated_apps/value_box/test_value_box_core_express.py b/tests/playwright/ai_generated_apps/value_box/test_value_box_core_express.py
new file mode 100644
index 000000000..37ab0b907
--- /dev/null
+++ b/tests/playwright/ai_generated_apps/value_box/test_value_box_core_express.py
@@ -0,0 +1,41 @@
+from playwright.sync_api import Page
+
+from shiny.playwright import controller
+from shiny.pytest import create_app_fixture
+from shiny.run import ShinyAppProc
+
+app = create_app_fixture(["app-core.py", "app-express.py"])
+
+
+def test_value_boxes(page: Page, app: ShinyAppProc) -> None:
+ page.goto(app.url)
+
+ # Test left-center value box
+ left_center_box = controller.ValueBox(page, "left_center_value_box")
+ left_center_box.expect_title("Revenue")
+ left_center_box.expect_value("$5.2M")
+
+ # Test top-right value box
+ top_right_box = controller.ValueBox(page, "top_right_value_box")
+ top_right_box.expect_title("Active Users")
+ top_right_box.expect_value("2.4K")
+ top_right_box.expect_height("200px")
+
+ # Test bottom value box
+ bottom_box = controller.ValueBox(page, "bottom_value_box")
+ bottom_box.expect_title("Conversion Rate")
+ bottom_box.expect_value("3.8%")
+ bottom_box.expect_height("200px")
+
+ # Test full screen value box
+ full_screen_box = controller.ValueBox(page, "full_screen_value_box")
+ full_screen_box.expect_title("Total Sales")
+ full_screen_box.expect_value("8,742")
+ full_screen_box.expect_height("600px")
+ full_screen_box.expect_full_screen_available(True)
+
+ # Test custom background value box
+ custom_bg_box = controller.ValueBox(page, "custom_bg_value_box")
+ custom_bg_box.expect_title("Pending Orders")
+ custom_bg_box.expect_value("156")
+ custom_bg_box.expect_height("200px")