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

fix: fix pywebview compatibility by using requirejs correctly #709

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
101 changes: 95 additions & 6 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,94 @@ jobs:
run:
pip install packages/solara-meta/dist/*.whl

webview-test:
needs: [build]
timeout-minutes: 15
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: false
matrix:
os: [macos]
python-version: ["3.10"]
env:
LOCK_FILE_LOCATION: .ci-package-locks/webview/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: |
mkdir -p .ci-package-locks/integration
if [ "${{ matrix.os }}" = "ubuntu" ]; then
pip install "pywebview[gtk]"
else
pip install "pywebview"
fi
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 pywebview
if: github.event_name != 'schedule' || steps.install_no_lock.outputs.HAS_DIFF == 'true'
run: |
python tests/webview/solara-pywebview-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]
Expand Down Expand Up @@ -395,12 +483,13 @@ jobs:
- 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/pytest-ipywidgets/dist/*.whl`[all,ipywidgets${{ matrix.ipywidgets_major }}]
pip install `echo packages/solara-meta/dist/*.whl`[dev,documentation]
pip install `echo packages/solara-enterprise/dist/*.whl`[ssg,auth]
pip install uv
uv pip install -r ${{ env.LOCK_FILE_LOCATION }}
uv pip install `echo dist/*.whl`[all]
uv pip install `echo packages/solara-server/dist/*.whl`[all]
uv pip install `echo packages/pytest-ipywidgets/dist/*.whl`[all,ipywidgets${{ matrix.ipywidgets_major }}]
uv pip install `echo packages/solara-meta/dist/*.whl`[dev,documentation]
uv pip install `echo packages/solara-enterprise/dist/*.whl`[ssg,auth]

- name: Install playwright
run: playwright install
Expand Down
22 changes: 13 additions & 9 deletions solara/server/templates/solara.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@
window.solara.browser_platform = true;
{% endif %}
var debug = true;

function loadApp(solaraVuetify) {
var appComponent = {
// if we do prerendering, keep it unmounted, and mount
// when loading becomes false
Expand Down Expand Up @@ -389,14 +389,14 @@
};
const vue3 = Vue.version.startsWith('3');
if(vue3) {
var app = Vue.createApp(appComponent).use(vuetifyPlugin)
window.app = Vue.createApp(appComponent).use(vuetifyPlugin)
.component("jupyter-widget-mount-point", jupyterWidgetMountPoint)
.mount('#app');

} else {
Vue.component('jupyter-widget-mount-point', jupyterWidgetMountPoint);
Vue.use(Vuetify);
var app = new Vue({
window.app = new Vue({
vuetify: new Vuetify({
theme: {
dark: inDarkMode(),
Expand All @@ -406,6 +406,7 @@
...appComponent,
});
}
}
// Loading classic notebook extensions.
{% if 'jupyter-vuetify/extension' in resources.nbextensions -%}
window.enable_nbextensions = true;
Expand Down Expand Up @@ -439,12 +440,15 @@
{% for ext in resources.nbextensions if ext != 'jupyter-vuetify/extension' and ext != 'jupyter-vue/extension' -%}
"{{root_path}}/static/nbextensions/{{ ext }}.js",
{% endfor %}
]);
(async function () {
await Promise.all([
solaraInit('solara-main')
]);
})();
], function (solaraVuetify) {
loadApp();
(async function () {
await Promise.all([
await solaraInit('solara-main')
]);
})();

});

</script>

Expand Down
6 changes: 3 additions & 3 deletions tests/unit/file_browser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def Test():
list: solara.components.file_browser.FileListWidget = div.children[1]
items = list.files
names = {k["name"] for k in items}
assert names == {"unit", "ui", "docs", "integration", ".."}
assert names == {"unit", "ui", "docs", "integration", "webview", ".."}


def test_file_browser_test_change_directory():
Expand Down Expand Up @@ -212,11 +212,11 @@ def set_directory(path: Path) -> None:
file_list.observe(mock, "files")
items = file_list.files
names = {k["name"] for k in items}
assert names == {"unit", "ui", "docs", "integration", ".."}
assert names == {"unit", "ui", "docs", "integration", "webview", ".."}
file_list.test_click("..")
assert mock.call_count == 0
file_list.test_click("integration")
items = file_list.files
names = {k["name"] for k in items}
assert names != {"unit", "ui", "docs", "integration", ".."}
assert names != {"unit", "ui", "docs", "integration", "webview", ".."}
assert mock.call_count == 1
56 changes: 56 additions & 0 deletions tests/webview/solara-pywebview-test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import sys
import threading
from time import sleep
from pathlib import Path

import click
import os


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):
os.environ["SOLARA_APP"] = str(HERE / "test_app.py")

import webview
import solara.server.starlette

server = solara.server.starlette.ServerStarlette(host="localhost", port=5001)
print(f"Starting server on {server.base_url}")
server.serve_threaded()
server.wait_until_serving()

def test(value):
print("test output", value)
window.destroy()
sys.exit(0)

def fail_guard():
sleep(15)
# dump html
html = window.evaluate_js("document.documentElement.outerHTML")
window.destroy()
print("failed")
if html:
with open("test-results/pywebview-failed.html", "w") as f:
f.write(html)
else:
print("no html")
sys.exit(1)

threading.Thread(target=fail_guard, daemon=True).start()
window = webview.create_window("Solara example app", server.base_url) # , resizable=True, width=1500, height=1500)#, on_top=True)
window.expose(test)
webview.start()
# server.join()


if __name__ == "__main__":
run()
13 changes: 13 additions & 0 deletions tests/webview/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import solara


@solara.component_vue("test_pywebview.vue")
def TestPywebview():
pass


@solara.component
def Page():
TestPywebview()
# html = "<script>pywebview.api.test(\"Test passes!\")</script>Script tag inserted"
# solara.HTML(unsafe_innerHTML=html)
11 changes: 11 additions & 0 deletions tests/webview/test_pywebview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<div>This component should call back to Python</div>
</template>
<script>
module.exports = {
mounted() {
console.log("mounted")
pywebview.api.test("Test passes!")
}
}
</script>
Loading