Skip to content
Merged
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
Binary file added tests/assets/multi_view/00_startup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/multi_view/00_startup_github.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,62 @@ def _build_ui(self):
)


class MultiView(TrameApp):
"""Two LocalViews sharing one WASM session (issues/76 and issues/77).

Regression check: with a shared session, an update targeting one view must
not blank the other. A blacked-out half exceeds the screenshot threshold.
"""

HALF = "width:50vw; height:100vh; display:inline-block; vertical-align:top;"

def __init__(self, server=None):
super().__init__(server)
enable_testing(self.server, "local_rendering_ready")
self.render_window_1 = self._create_pipeline(
vtk.vtkConeSource(), (0.1, 0.2, 0.4)
)
self.render_window_2 = self._create_pipeline(
vtk.vtkSphereSource(), (0.4, 0.2, 0.1)
)
self._build_ui()

@staticmethod
def _create_pipeline(source, background):
renderer = vtk.vtkRenderer()
rw = vtk.vtkRenderWindow()
rw.AddRenderer(renderer)
rwi = vtk.vtkRenderWindowInteractor(render_window=rw)
rwi.interactor_style.SetCurrentStyleToTrackballCamera()

mapper = vtk.vtkPolyDataMapper(input_connection=source.output_port)
actor = vtk.vtkActor(mapper=mapper)

renderer.AddActor(actor)
renderer.background = background
renderer.ResetCamera()

return rw

def _build_ui(self):
# Each view bumps the counter once when it first finishes updating, so
# the test waits for it to reach 2 (both views rendered).
self.state.local_rendering_ready = 0
with DivLayout(self.server) as self.ui:
html.Div("{{ local_rendering_ready }}", classes="readyCount")
client.Style(
"body { margin: 0; } .readyCount { z-index: 10; position: absolute; left: 0; top: 0; }"
)
with html.Div(style=self.HALF):
vtklocal.LocalView(
self.render_window_1, updated="local_rendering_ready++"
)
with html.Div(style=self.HALF):
vtklocal.LocalView(
self.render_window_2, updated="local_rendering_ready++"
)


@pytest.fixture
def ref_dir() -> Path:
return Path(__file__).parent / "refs"
Expand Down Expand Up @@ -244,6 +300,11 @@ def ConeApp():
return Cone


@pytest.fixture
def MultiViewApp():
return MultiView


@pytest.fixture
def VolumeApp():
return VolumeRendering
54 changes: 54 additions & 0 deletions tests/test_multi_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import asyncio
from pathlib import Path
from trame_vtklocal.module.wasm import wasm_downloaded

import pytest
from playwright.async_api import async_playwright, expect

BASELINES = [
Path(__file__).with_name("assets") / "multi_view" / name
for name in [
"00_startup",
]
]


@pytest.mark.asyncio
async def test_multi_view(MultiViewApp, utils):
"""Two LocalViews sharing one WASM session must both render (issues/76, /77).

Catches the updateAsync-serialization regression (a second view's update
being swallowed leaves it black). It does NOT catch the Size-property leak
on Linux/OSMesa: there the vtkOSOpenGLRenderWindow -> vtkRenderWindow remap
makes even the old skip match, so that bug only reproduces on a platform
whose render window serializes under its own child class (e.g. macOS).
"""
app = MultiViewApp("multi-view")
task = app.server.start(exec_mode="task", port=0)
await app.server.ready
await wasm_downloaded()
RESULT_BASE = Path(__file__).with_name("results") / "multi_view"
valid_image_comparisons = []

async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.set_viewport_size({"width": 600, "height": 300})

await page.goto(f"http://localhost:{app.server.port}/")
await asyncio.sleep(0.1) # wait for page load
# Both views increment the counter once they have rendered.
await expect(page.locator(".readyCount")).to_have_text("2")
valid_image_comparisons.append(
await utils.compare_screenshot(
page, BASELINES[0], RESULT_BASE, threshold=0.1
)
)

assert all(valid_image_comparisons), "Some images don't match"

# Clean up resource
await browser.close()

await app.server.stop()
await task
Loading