Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

test: Test apps locally before deploying via pytest fixtures. #1055

Merged
merged 45 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a0da1e5
Move deploy tests under playwright-shiny
karangattu Jan 23, 2024
156ee9e
pyright changes
karangattu Jan 23, 2024
70b975f
move it to a different directory
karangattu Jan 24, 2024
ffea0a1
Merge branch 'main' into deploy-tests-under-playwright-shiny
karangattu Jan 24, 2024
7658a52
Modify path to deploys
karangattu Jan 24, 2024
1a202b7
Modify path to deploys tests
karangattu Jan 24, 2024
da0f7fe
don't skip plotly app
karangattu Jan 24, 2024
dc18116
black formatting
karangattu Jan 24, 2024
0e31c72
rename the shinyapps.io json files
karangattu Jan 25, 2024
9ea2628
Rename the plotly shinyapps.io json file
karangattu Jan 25, 2024
20639a0
Merge branch 'main' into deploy-tests-under-playwright-shiny
schloerke Jan 25, 2024
ac1b0b7
Add plotly deploys app to transformer error list. Expand on path name…
schloerke Jan 25, 2024
15cb922
Missed app name update
schloerke Jan 25, 2024
1119b36
Discard changes to ...playwright/deploys/apps/plotly_app/app.py
schloerke Jan 25, 2024
1e23071
Revert playwright-deploys job and makefile entry
schloerke Jan 25, 2024
756f213
Move deploys apps
schloerke Jan 25, 2024
4c8c490
Deploy app to consistent locations
schloerke Jan 25, 2024
d18671b
Ignore more apps
schloerke Jan 25, 2024
a3b5880
Merge branch 'main' into deploy-tests-under-playwright-shiny
schloerke Jan 25, 2024
6ff659a
rename the rsconnect json files
karangattu Jan 25, 2024
518d575
Delete app.py
schloerke Jan 29, 2024
a25e410
Revert init deletion
schloerke Jan 29, 2024
e31dba9
Add flag to/not to deploy _deploys_ apps during testing
schloerke Jan 29, 2024
5e23d00
Pass through yielded app to write logic once
schloerke Jan 29, 2024
a656d32
Make fixture that deploys or runs an app locally
schloerke Jan 29, 2024
cd46c05
Use new fixture
schloerke Jan 29, 2024
e20575e
Add comment of debug options
schloerke Jan 29, 2024
3ef4aab
Merge branch 'main' into deploy-tests-under-playwright-shiny
schloerke Jan 29, 2024
949569f
pyright lint
schloerke Jan 29, 2024
a8b530a
Delete test_deploys_examples.py
schloerke Jan 29, 2024
3e8c5d1
Update deploy_utils.py
schloerke Jan 29, 2024
e483251
Make CI playwright more descriptive when testing
schloerke Jan 29, 2024
fd8f5c8
Update pytest.yaml
schloerke Jan 29, 2024
db70485
Explicitly wait up to 2 mins for text to resolve
schloerke Jan 29, 2024
d1107ed
remove fastapi from app_requirements.txt
karangattu Jan 30, 2024
2e239a8
add timeout to page default
karangattu Jan 30, 2024
dcda99b
Move pre-test to be in deploys GHA job. Only deploy if on push or PR …
schloerke Jan 30, 2024
fda6e8b
Remove skip if python version != 3.10
schloerke Jan 30, 2024
66cdbf9
Test deploy apps using 12 processes
schloerke Jan 30, 2024
dd80734
Do not use dynamic locator property
schloerke Jan 30, 2024
67d2e49
Get the __file__ info from request in fixture
schloerke Jan 30, 2024
72aa4c5
Do not first use locator that must internally resolve. Instead, nativ…
schloerke Jan 30, 2024
168aad9
Use correct deploy name
schloerke Jan 31, 2024
1838875
Shorten folder names
schloerke Jan 31, 2024
5c3b353
Rename deploy files
schloerke Jan 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
- name: Run End-to-End tests
timeout-minutes: 20
run: |
make playwright-shiny
make playwright-shiny SUB_FILE=". -vv"

playwright-examples:
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -99,7 +99,15 @@ jobs:
- name: Run example app tests
timeout-minutes: 20
run: |
make playwright-examples
make playwright-examples SUB_FILE=". -vv"

- name: Test deploys example apps on python 3.10
schloerke marked this conversation as resolved.
Show resolved Hide resolved
if: ${{ matrix.python-version == '3.10' }}
schloerke marked this conversation as resolved.
Show resolved Hide resolved
timeout-minutes: 5
env:
DEPLOY_APPS: "false"
run: |
make playwright-deploys SUB_FILE=". -vv"

playwright-deploys:
# Only allow one `playwright-deploys` job to run at a time. (Independent of branch / PR)
Expand All @@ -121,15 +129,15 @@ jobs:

- name: Run tests for deploys
env:
DEPLOY_APPS: "true"
DEPLOY_CONNECT_SERVER_URL: "https://rsc.radixu.com/"
DEPLOY_CONNECT_SERVER_API_KEY: "${{ secrets.DEPLOY_CONNECT_SERVER_API_KEY }}"
DEPLOY_SHINYAPPS_NAME: "${{ secrets.DEPLOY_SHINYAPPS_NAME }}"
DEPLOY_SHINYAPPS_TOKEN: "${{ secrets.DEPLOY_SHINYAPPS_TOKEN }}"
DEPLOY_SHINYAPPS_SECRET: "${{ secrets.DEPLOY_SHINYAPPS_SECRET }}"
timeout-minutes: 30
run: |
make playwright-deploys

make playwright-deploys SUB_FILE=". -vv"
pypi:
name: "Deploy to PyPI"
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ docs/source/reference/

# Developer scratch area
_dev/
tests/playwright/deploys/apps/*/requirements.txt
tests/playwright/deploys/**/requirements.txt
8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ test: ## run tests quickly with the default Python
# Default `SUB_FILE` to empty
SUB_FILE:=

DEPLOYS_FILE:=tests/playwright/deploys

install-playwright:
playwright install --with-deps

Expand All @@ -100,12 +98,12 @@ install-rsconnect: ## install the main version of rsconnect till pypi version su
playwright-shiny: install-playwright ## end-to-end tests with playwright
pytest tests/playwright/shiny/$(SUB_FILE)

playwright-deploys: install-playwright install-rsconnect ## end-to-end tests on examples with playwright
pytest tests/playwright/deploys/$(SUB_FILE)

playwright-examples: install-playwright ## end-to-end tests on examples with playwright
pytest tests/playwright/examples/$(SUB_FILE)

playwright-deploys: install-playwright install-rsconnect ## end-to-end tests on deploys with playwright
pytest tests/playwright/deploys/$(SUB_FILE) -s

testrail-junit: install-playwright install-trcli ## end-to-end tests with playwright and generate junit report
pytest tests/playwright/shiny/$(SUB_FILE) --junitxml=report.xml

Expand Down
4 changes: 2 additions & 2 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"sandbox",
"_dev",
"docs",
"tests/playwright/deploys/apps",
"tests/playwright/deploys/*/app.py",
"shiny/templates"
],
"typeCheckingMode": "strict",
"reportImportCycles": "none",
"reportUnusedFunction": "none",
"reportPrivateUsage": "none",
"reportUnnecessaryIsInstance": "none",
"reportUnnecessaryIsInstance": "none"
}
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[pytest]
asyncio_mode=strict
testpaths=tests/pytest/
; ; Debug version of options
; addopts = --strict-markers --durations=6 --durations-min=5.0 --browser chromium --numprocesses auto --video=retain-on-failure -vv
addopts = --strict-markers --durations=6 --durations-min=5.0 --browser webkit --browser firefox --browser chromium --numprocesses auto
markers =
examples: Suite of tests to validate that examples do not produce errors (deselect with '-m "not examples"')
Expand Down
73 changes: 42 additions & 31 deletions tests/playwright/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@
from pathlib import PurePath
from time import sleep
from types import TracebackType
from typing import IO, Any, Callable, Generator, List, Optional, TextIO, Type, Union
from typing import (
IO,
Any,
Callable,
Generator,
List,
Literal,
Optional,
TextIO,
Type,
Union,
)

import pytest

Expand Down Expand Up @@ -176,25 +187,37 @@ def run_shiny_app(
return sa


def create_app_fixture(app: Union[PurePath, str], scope: str = "module"):
def local_app_fixture_gen(app: PurePath | str):
sa = run_shiny_app(app, wait_for_start=False)
try:
with sa:
sa.wait_until_ready(30)
yield sa
finally:
logging.warning("Application output:\n" + str(sa.stderr))


ScopeName = Literal["session", "package", "module", "class", "function"]


def create_app_fixture(
app: Union[PurePath, str],
scope: ScopeName = "module",
):
@pytest.fixture(scope=scope)
def fixture_func():
sa = run_shiny_app(app, wait_for_start=False)
try:
with sa:
sa.wait_until_ready(30)
yield sa
finally:
logging.warning("Application output:\n" + str(sa.stderr))
# Pass through `yield` via `next(...)` call
# (`yield` must be on same line as `next`!)
app_gen = local_app_fixture_gen(app)
yield next(app_gen)

return pytest.fixture(
scope=scope, # type: ignore
)(fixture_func)
return fixture_func


def create_example_fixture(
example_name: str,
example_file: str = "app.py",
scope: str = "module",
scope: ScopeName = "module",
):
"""Used to create app fixtures from apps in py-shiny/examples"""
return create_app_fixture(
Expand All @@ -205,7 +228,7 @@ def create_example_fixture(
def create_doc_example_fixture(
example_name: str,
example_file: str = "app.py",
scope: str = "module",
scope: ScopeName = "module",
):
"""Used to create app fixtures from apps in py-shiny/shiny/api-examples"""
return create_app_fixture(
Expand All @@ -215,28 +238,21 @@ def create_doc_example_fixture(

def create_doc_example_core_fixture(
example_name: str,
scope: str = "module",
scope: ScopeName = "module",
):
"""Used to create app fixtures from ``app-core.py`` example apps in py-shiny/shiny/api-examples"""
return create_doc_example_fixture(example_name, "app-core.py", scope)


def create_doc_example_express_fixture(
example_name: str,
scope: str = "module",
scope: ScopeName = "module",
):
"""Used to create app fixtures from ``app-express.py`` example apps in py-shiny/shiny/api-examples"""
return create_doc_example_fixture(example_name, "app-express.py", scope)


def create_deploys_fixture(app: Union[PurePath, str], scope: str = "module"):
"""Used to create app fixtures from apps in tests/playwright/deploys/apps"""
return create_app_fixture(
here_root / "tests/playwright/deploys/apps" / app / "app.py", scope
)


def x_create_doc_example_fixture(example_name: str, scope: str = "module"):
def x_create_doc_example_fixture(example_name: str, scope: ScopeName = "module"):
"""Used to create app fixtures from apps in py-shiny/shiny/examples"""
return create_app_fixture(
here_root / "shiny/experimental/api-examples" / example_name / "app.py", scope
Expand All @@ -245,13 +261,8 @@ def x_create_doc_example_fixture(example_name: str, scope: str = "module"):

@pytest.fixture(scope="module")
def local_app(request: pytest.FixtureRequest) -> Generator[ShinyAppProc, None, None]:
sa = run_shiny_app(PurePath(request.path).parent / "app.py", wait_for_start=False)
try:
with sa:
sa.wait_until_ready(30)
yield sa
finally:
logging.warning("Application output:\n" + str(sa.stderr))
app_gen = local_app_fixture_gen(PurePath(request.path).parent / "app.py")
yield next(app_gen)


@contextmanager
Expand Down
16 changes: 15 additions & 1 deletion tests/playwright/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2170,7 +2170,21 @@ def __init__(
super().__init__(page, id=id, loc=f"#{id}.shiny-text-output")


# TODO-Karan: Add OutputCode class
class OutputCode(_OutputTextValue):
def __init__(self, page: Page, id: str) -> None:
super().__init__(page, id=id, loc=f"pre#{id}.shiny-text-output")

def expect_has_placeholder(
self, placeholder: bool = False, *, timeout: Timeout = None
) -> None:
_expect_class_value(
self.loc,
cls="noplaceholder",
has_class=not placeholder,
timeout=timeout,
)


class OutputTextVerbatim(_OutputTextValue):
def __init__(self, page: Page, id: str) -> None:
super().__init__(page, id=id, loc=f"pre#{id}.shiny-text-output")
Expand Down
1 change: 0 additions & 1 deletion tests/playwright/deploys/apps/__init__.py

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def summary_data():
height="100%",
)

@reactive.calc
@reactive.Calc
def filtered_df():
# input.summary_data_selected_rows() is a tuple, so we must convert it to list,
# as that's what Pandas requires for indexing.
Expand Down Expand Up @@ -114,7 +114,7 @@ def synchronize_size(output_id):
def wrapper(func):
input = session.get_current_session().input

@reactive.effect
@reactive.Effect
def size_updater():
func(
input[f".clientdata_output_{output_id}_width"](),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pandas
plotly
git+https://github.com/posit-dev/py-htmltools.git#egg=htmltools
git+https://github.com/posit-dev/py-shinywidgets.git#egg=shinywidgets
fastapi==0.108.0

22 changes: 22 additions & 0 deletions tests/playwright/deploys/plotly/test_plotly_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from playwright.sync_api import Page, expect
from utils.deploy_utils import (
create_deploys_app_url_fixture,
skip_if_not_python_310_or_chrome,
)

TIMEOUT = 2 * 60 * 1000
app_url = create_deploys_app_url_fixture(__file__, "example_deploy_app_A")


@skip_if_not_python_310_or_chrome
def test_deploys(page: Page, app_url: str) -> None:
page.goto(app_url)

COUNTRY = "Afghanistan"
expect(page.get_by_text(COUNTRY)).to_have_count(1, timeout=TIMEOUT)
page.get_by_role("cell", name=COUNTRY).click()
expect(page.locator("#country_detail_pop")).to_contain_text(
COUNTRY, timeout=TIMEOUT
)
expect(page.locator("#country_detail_percap")).to_contain_text(COUNTRY)
expect(page.get_by_text(COUNTRY)).to_have_count(3)
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
git+https://github.com/posit-dev/py-htmltools.git#egg=htmltools
fastapi==0.108.0

20 changes: 20 additions & 0 deletions tests/playwright/deploys/shiny-express-accordion/test_accordion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from controls import Accordion
from playwright.sync_api import Page
from utils.deploy_utils import (
create_deploys_app_url_fixture,
skip_if_not_python_310_or_chrome,
)

app_url = create_deploys_app_url_fixture(__file__, "shiny_express_accordion")


@skip_if_not_python_310_or_chrome
schloerke marked this conversation as resolved.
Show resolved Hide resolved
def test_express_accordion(page: Page, app_url: str) -> None:
page.goto(app_url)

acc = Accordion(page, "express_accordion")
acc_panel_2 = acc.accordion_panel("Panel 2")
acc_panel_2.expect_open(True)
acc_panel_2.expect_body("n = 50")
acc_panel_2.set(False)
acc_panel_2.expect_open(False)
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
git+https://github.com/posit-dev/py-htmltools.git#egg=htmltools
pandas
fastapi==0.108.0

16 changes: 16 additions & 0 deletions tests/playwright/deploys/shiny-express-dataframe/test_dataframe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from controls import OutputDataFrame
from playwright.sync_api import Page
from utils.deploy_utils import (
create_deploys_app_url_fixture,
skip_if_not_python_310_or_chrome,
)

app_url = create_deploys_app_url_fixture(__file__, "shiny-express-dataframe")


@skip_if_not_python_310_or_chrome
def test_express_dataframe_deploys(page: Page, app_url: str) -> None:
page.goto(app_url)

dataframe = OutputDataFrame(page, "sample_data_frame")
dataframe.expect_n_row(6)
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
git+https://github.com/posit-dev/py-htmltools.git#egg=htmltools
folium
fastapi==0.108.0

25 changes: 25 additions & 0 deletions tests/playwright/deploys/shiny-express-folium/test_folium.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from playwright.sync_api import Page, expect
from utils.deploy_utils import (
create_deploys_app_url_fixture,
skip_if_not_python_310_or_chrome,
)

app_url = create_deploys_app_url_fixture(__file__, "shiny-express-folium")


@skip_if_not_python_310_or_chrome
def test_folium_map(page: Page, app_url: str) -> None:
page.goto(app_url)

expect(page.get_by_text("Static Map")).to_have_count(1)
expect(page.get_by_text("Map inside of render express call")).to_have_count(1)
# map inside the @render.express
expect(
page.frame_locator("iframe").nth(1).get_by_role("link", name="OpenStreetMap")
).to_have_count(1)
# map outside of the @render.express at the top level
expect(
page.frame_locator("iframe")
.nth(0)
.get_by_role("link", name="U.S. Geological Survey")
).to_have_count(1)
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
git+https://github.com/posit-dev/py-htmltools.git#egg=htmltools
fastapi==0.108.0

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"app_url": "https://testing-apps.shinyapps.io/shiny_express_page_default/",
"app_id": 10800233,
"app_guid": null,
"title": "shiny_express_page_default",
"title": "shiny-express-folium",
"app_mode": "python-shiny",
"app_store_version": 1
}
Expand Down
Loading
Loading