diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1b59a1bf1..d5fea41e8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -320,6 +320,85 @@ jobs: name: ci-package-locks-pyinstaller-os${{ matrix.os }}-python${{ matrix.python-version }}-ipywidgets${{ matrix.ipywidgets_major }} path: ./**/${{ env.LOCK_FILE_LOCATION }} + qt-test: + needs: [build] + timeout-minutes: 15 + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [macos, windows, ubuntu] + # https://pypi.org/project/PySide6/ seems to have binaries only for python 3.9 + python-version: ["3.9"] + env: + LOCK_FILE_LOCATION: .ci-package-locks/qt/os${{ matrix.os }}-python${{ matrix.python-version }}-ipywidgets${{ matrix.ipywidgets_major }}.txt + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - uses: actions/download-artifact@v4 + with: + name: solara-builds-${{ github.run_number }} + + - name: Link solara app package + if: matrix.os != 'windows' + run: | + cd packages/solara-vuetify-app + npm run devlink + - name: Copy solara app package + if: matrix.os == 'windows' + run: | + cd packages/solara-vuetify-app + npm run wincopy + - name: Prepare + id: prepare + run: | + mkdir test-results + if [ -f ${{ env.LOCK_FILE_LOCATION }} ]; then + echo "LOCKS_EXIST=true" >> "$GITHUB_OUTPUT" + else + echo "LOCKS_EXIST=false" >> "$GITHUB_OUTPUT" + fi + - name: Install without locking versions + if: github.event_name == 'schedule' || steps.prepare.outputs.LOCKS_EXIST == 'false' + id: install_no_lock + run: | + pip install pyside6 qtpy + pip install `echo dist/*.whl`[all] + pip install `echo packages/solara-server/dist/*.whl`[all] + pip install `echo packages/solara-meta/dist/*.whl`[dev,documentation] + pip freeze --exclude solara --exclude solara-ui --exclude solara-server > ${{ env.LOCK_FILE_LOCATION }} + git diff --quiet || echo "HAS_DIFF=true" >> "$GITHUB_OUTPUT" + - name: Install + if: github.event_name != 'schedule' && steps.prepare.outputs.LOCKS_EXIST == 'true' + run: | + pip install -r ${{ env.LOCK_FILE_LOCATION }} + pip install `echo dist/*.whl`[all] + pip install `echo packages/solara-server/dist/*.whl`[all] + pip install `echo packages/solara-meta/dist/*.whl`[dev,documentation] + - name: test qt app + if: github.event_name != 'schedule' || steps.install_no_lock.outputs.HAS_DIFF == 'true' + run: | + python tests/qtapp/solara-qt-test.py + - name: Upload Test artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-integration-os${{ matrix.os }}-python${{ matrix.python-version }}-ipywidgets${{ matrix.ipywidgets_major }} + path: test-results + + - name: Upload CI package locks + if: steps.install_no_lock.outputs.HAS_DIFF == 'true' || steps.prepare.outputs.LOCKS_EXIST == 'false' + uses: actions/upload-artifact@v4 + with: + name: ci-package-locks-integration-os${{ matrix.os }}-python${{ matrix.python-version }}-ipywidgets${{ matrix.ipywidgets_major }} + path: ./**/${{ env.LOCK_FILE_LOCATION }} + integration-test: needs: [build] timeout-minutes: 15 diff --git a/tests/qtapp/solara-qt-test.py b/tests/qtapp/solara-qt-test.py new file mode 100644 index 000000000..321cbcb91 --- /dev/null +++ b/tests/qtapp/solara-qt-test.py @@ -0,0 +1,64 @@ +import sys +import threading +from time import sleep +from pathlib import Path + +import click +import os + +# make sure you use pyside when distributing your all without having to use a GPL license +from qtpy.QtWidgets import QApplication +from qtpy.QtWebEngineWidgets import QWebEngineView +from qtpy import QtCore + + +HERE = Path(__file__).parent + + +@click.command() +@click.option( + "--port", + default=int(os.environ.get("PORT", 0)), + help="Port to run the server on, 0 for a random free port", +) +def run(port: int): + sys.path.append(str(HERE)) + os.environ["SOLARA_APP"] = "test_app" + import test_app + + import solara.server.starlette + + server = solara.server.starlette.ServerStarlette(host="localhost", port=port) + print(f"Starting server on {server.base_url}") + server.serve_threaded() + server.wait_until_serving() + + def test_success(value): + print("test output", value) + app.quit() + server.stop_serving() + + test_app.callback = test_success # type: ignore + + failed = False + + def fail_guard(): + sleep(10) + nonlocal failed + print("failed") + app.quit() + failed = True + + app = QApplication([""]) + web = QWebEngineView() + web.setUrl(QtCore.QUrl(server.base_url)) + web.show() + + threading.Thread(target=fail_guard, daemon=True).start() + app.exec_() + if failed: + sys.exit(1) + + +if __name__ == "__main__": + run() diff --git a/tests/qtapp/test_app.py b/tests/qtapp/test_app.py new file mode 100644 index 000000000..4d2e6a5a1 --- /dev/null +++ b/tests/qtapp/test_app.py @@ -0,0 +1,17 @@ +import solara + + +def callback(event): + print("Event received:", event) + + +@solara.component_vue("test_pywebview.vue") +def TestPywebview(event_callback): + pass + + +@solara.component +def Page(): + TestPywebview(event_callback=callback) + # html = "Script tag inserted" + # solara.HTML(unsafe_innerHTML=html) diff --git a/tests/qtapp/test_pywebview.vue b/tests/qtapp/test_pywebview.vue new file mode 100644 index 000000000..af9e2b52f --- /dev/null +++ b/tests/qtapp/test_pywebview.vue @@ -0,0 +1,11 @@ + +