From 2bb0f815f73393ba6a8c74a234e5f057edee2b89 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:16:29 -0600 Subject: [PATCH 1/7] ci: add more testing --- .github/workflows/test_and_release.yml | 119 ++++++++++----------- pyproject.toml | 3 + tests/.gitignore | 1 + tests/assets/cone/00_startup.png | Bin 0 -> 1965 bytes tests/assets/cone/01_update_resolution.png | Bin 0 -> 4130 bytes tests/assets/cone/02_unmount.png | Bin 0 -> 1189 bytes tests/assets/cone/03_remount.png | Bin 0 -> 1895 bytes tests/conftest.py | 113 ++++++++++++++++++- tests/requirements.txt | 4 - tests/test_cone.py | 64 +++++++++++ 10 files changed, 235 insertions(+), 69 deletions(-) create mode 100644 tests/.gitignore create mode 100644 tests/assets/cone/00_startup.png create mode 100644 tests/assets/cone/01_update_resolution.png create mode 100644 tests/assets/cone/02_unmount.png create mode 100644 tests/assets/cone/03_remount.png delete mode 100644 tests/requirements.txt create mode 100644 tests/test_cone.py diff --git a/.github/workflows/test_and_release.yml b/.github/workflows/test_and_release.yml index 1a89505..f5ee4a3 100644 --- a/.github/workflows/test_and_release.yml +++ b/.github/workflows/test_and_release.yml @@ -2,25 +2,28 @@ name: Test and Release on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: - python-version: "3.9" + version: "0.11.24" + enable-cache: true + - name: Install the project + run: uv sync --all-extras --dev # Install and run pre-commit - run: | - pip install pre-commit - pre-commit install - pre-commit run --all-files + uv run pre-commit install + uv run pre-commit install --hook-type commit-msg + uv run pre-commit run --all-files pytest: name: Pytest ${{ matrix.config.name }} @@ -30,10 +33,7 @@ jobs: matrix: python-version: ["3.10"] config: - - { - name: "Linux", - os: ubuntu-latest - } + - { name: "Linux", os: ubuntu-latest } # - { # name: "MacOSX", # os: macos-latest @@ -48,57 +48,54 @@ jobs: shell: bash steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install OSMesa for Linux - if: matrix.config.os == 'ubuntu-latest' - run: | - sudo apt update - sudo apt-get install -y libosmesa6-dev - - - name: Install dependencies - run: | - pip install .[vtk] - pip install -r tests/requirements.txt - pip install coverage - playwright install - - - name: Run Tests - run: | - # Run the tests with coverage so we get a coverage report too - coverage run --source . -m pytest ./tests - # Print the coverage report - coverage report -m - - - name: Upload Coverage to Codecov - uses: codecov/codecov-action@v3 - - - name: Upload pytest test results - uses: actions/upload-artifact@v4 - if: always() - with: - name: pytest-results-${{ matrix.config.name }} - path: | - tests/refs/*.yml - tests/refs/**/*.png - assets/** - retention-days: 1 + - name: Checkout + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + version: "0.11.24" + enable-cache: true + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Install OSMesa for Linux + if: matrix.config.os == 'ubuntu-latest' + run: | + sudo apt update + sudo apt-get install -y libosmesa6-dev + + - name: Install dependencies + run: | + uv sync --all-extras --dev + source .venv/bin/activate + playwright install + pytest -s ./tests --cov=src --cov-report=xml + + - name: Upload Coverage to Codecov + uses: codecov/codecov-action@v3 + + - name: Upload pytest test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: pytest-results-${{ matrix.config.name }} + path: | + tests/refs/*.yml + tests/refs/**/*.png + assets/** + retention-days: 1 test-npm-build: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set Up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 24 @@ -109,11 +106,7 @@ jobs: npm run build release: - needs: [ - pre-commit, - pytest, - test-npm-build, - ] + needs: [pre-commit, pytest, test-npm-build] runs-on: ubuntu-latest if: github.event_name == 'push' environment: @@ -125,12 +118,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set Up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 24 diff --git a/pyproject.toml b/pyproject.toml index afb2498..fd6a579 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,9 @@ dev = [ "ruff", "pytest", "pytest-asyncio", + "pytest-cov >=3", + "coverage", + "pytest-playwright", "trame-client[test]", ] diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..1a06816 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +results diff --git a/tests/assets/cone/00_startup.png b/tests/assets/cone/00_startup.png new file mode 100644 index 0000000000000000000000000000000000000000..9619157fc4625940f17d33d9306239e522463717 GIT binary patch literal 1965 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 literal 0 HcmV?d00001 diff --git a/tests/assets/cone/02_unmount.png b/tests/assets/cone/02_unmount.png new file mode 100644 index 0000000000000000000000000000000000000000..04d28c7357abcf74455e6c267977f01ee1ed8f01 GIT binary patch literal 1189 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_qWWPZ!6KiaBp?FZ6mADbw)q zzqyZ8+M$L8gXV0iph^5M~=M-Lr3bmhvGBS(&0 zx^(IBD=JJ-fHJck0xs{UX<|UGwwv+qQi>dqRxc?%lgTfBw9A^Je+k&!0bk ze}Dh^GiPFAV|fom^!)p5Wo2$|ZfRLrQ)9!B{PFbb7pG63e){z3#fuk#M(PW`d;3;b zPft%@pZ~y)Kf({ce*Jpz;K7?WZ)TWCH5C5TKD2!Ka-i7Z!-wbF*Yh2asQo9rZ|~mP z8oxJh-uU?Y|KDkAYb)EZ@y8y{?c27!d;h-v_u<#CU;q61^VO?Y3_V9P>g(%^i$C9Y zx3BxNWAEO--`?J4`19XwnZ|Z^l5lCNC1oQa1fc~Ha0IN|rMgv0>*`ZsiD_lNJyXP!Cpea;Wh zIVUe7jJ|Wb%XSzHwlgG{%7(#gi0>bK3#6>NI|DG7om&X?v(uL==SM=5xHJ2H*ZKFq zl8nmIM$w|Jq2C1V74_R=ErA-Hx}m(gWc;6bkxDoxIInkL!RQ}7m>_9rl;pM zx2t+*&50Rdbi)oo+(NdMZmO)9x}IBPi%B>E`#fNL^HSEDeq}8cHk~fCSFBr_yB0PL zi``xcX-y0?kEF~0P4BlVOvYiT9l z)I0VFvL=QXIYOOlhkhKEZ)7EyR@Zr(-?nF_z^0onFD~}Y%`MF>wFdmF)24jXya48_ z-o&;C!0b;w|9}sGJ_t%V9LoZo4p87IESTq{q{9EV@)u2+_dLPx(Qjf%`-0r$qy?OMmL5cPV=c>O({&5A#j-XK9SVzB_N( zGS=uPz8*BjA9I2vw#@C!E`Qg0KW`kZ>EnO?hk!hwWU@VJpjHelqjWohB`5Myad^KX z%V1wxZQs!l$y^(ll(xH~5Pl3ptHCG>LL_NdNRrz?kJQ~mH{o;- z+Jzv{<2G;ysq>VnEr~Ai&$;KK86vJ0S6Nx7eoH-$!Z(TF2{>A{mlB4*SPB=&)Hf0b z`xx)79wm_Y2(GF+uMxwZzYWj#P>tPcj9|~x_dR0`zju0+OyY0js(#IDbYY(_g&*=z zt=?*6u+Qhg4;@f_bh@!H<5WRIi0JJ_dsiJ{kO_uLVhgCRF!(_ls40#;%y5r?=qDA& zc5{`aHW3cRr^`485E*pQeFVQ&#wom)>s;6Kv8;RwIYGFYCuXu)2&`5-VWp~f1gC`o zaX5UhbR>P?NE8|$)`g2Z+%R#8sMG+ZLSXA-ENd}i5Dh(~3Ads;BX}cp4*yiGiWomE^T8_l}vY$kZ zK)=j=p~ZKA=({s@2Pxb86GVXQc;|fxUxq{Y^(QjU;K@XnDVO@1StPsQOZh$c1J+f% zNK9xcfso^h7eRpyRy}dMvGX1uQ$kFJ5{=YeJ7z2>t{SR3PG2H3+8YEf8!$v6SG%l~?E&0*u?YNi_0x9&pt z*_-Rb!S7kZJ4mclGOoELMyOL3{uOZcRN7MPDdH* zs+b&KbIWJonPMK3%5gTg(7~5tULBQVV{WN0Ousanjw$6ia+Q>BxjR5Cv|jO0UQ^4% z0pd05l`l0u{zp1AYR594AR*U@pJQ!ffX&73T9l5Z?;-$|mDaZCP{VA*@TUawpbi;E z5k3ru^6w7i|7(4h=2q$lMuBVTPX9pu@b}hN)y&I*Mgb8RDYxz|gT{#rjZ=%dXl~$G zkHhi>f1@Cgq+=Kb`++PQv#%H8RG_dY94yn*Mer#)r_?a((+}A&Ud-W2+^1$q6!Ygl z6appg=UxL4Hbv!D@H>-(geCGJ=0S2|UkqrJl0s<^4g^CO{gMu04S6b*sx`#ZT6*1wKJ87?IK4k@q@S{Su~5_9dp1%695 literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py index b74bfa9..2300e1c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,110 @@ -import pytest from pathlib import Path -from trame_client.utils.testing import FixtureHelper + +import pytest +import vtk +from PIL import Image +from pixelmatch.contrib.PIL import pixelmatch +from trame.app import TrameApp +from trame.decorators import change +from trame.ui.html import DivLayout +from trame_client.utils.testing import FixtureHelper, enable_testing + +from trame.widgets import client, html, vtklocal +from trame_vtklocal.utils import ui ROOT_PATH = Path(__file__).parent.parent.absolute() HELPER = FixtureHelper(ROOT_PATH) +class Utils: + @staticmethod + async def assert_screenshot(page, baseline_image, result_directory, threshold=0.1): + test_image = result_directory / baseline_image.name + await page.screenshot(path=test_image) + + img_test = Image.open(test_image) + img_ref = Image.open(baseline_image) + img_diff = Image.new("RGBA", img_ref.size) + + file_diff = (test_image.parent / test_image.stem).with_suffix(".diff.png") + mismatch = pixelmatch(img_ref, img_test, img_diff, threshold=threshold) + img_diff.save(file_diff) + assert mismatch < threshold + + +def create_vtk_pipeline(): + renderer = vtk.vtkRenderer() + rw = vtk.vtkRenderWindow() + rw.AddRenderer(renderer) + rwi = vtk.vtkRenderWindowInteractor(render_window=rw) + rwi.interactor_style.SetCurrentStyleToTrackballCamera() + + cone = vtk.vtkConeSource() + + mapper = vtk.vtkPolyDataMapper(input_connection=cone.output_port) + actor = vtk.vtkActor(mapper=mapper) + + renderer.AddActor(actor) + renderer.background = (0.1, 0.2, 0.4) + renderer.ResetCamera() + + return rw, cone + + +class Cone(TrameApp): + def __init__(self, server=None, mode="wasm32", exec="sync", rendering="webgl"): + super().__init__(server) + self.state.wasm_conf = { + "mode": mode, + "exec": exec, + "rendering": rendering, + } + enable_testing(self.server, "local_rendering_ready") + self.render_window, self.cone = create_vtk_pipeline() + self._build_ui() + + @property + def resolution(self): + return self.state.resolution + + @resolution.setter + def resolution(self, v): + with self.state: + self.state.resolution = int(v) + + @change("resolution") + def _on_resolution(self, resolution, **_): + self.cone.resolution = int(resolution) + self.ctx.view.update() + + @property + def mounted(self): + return self.state.mounted + + @mounted.setter + def mounted(self, v): + with self.state: + self.state.mounted = bool(v) + + def _build_ui(self): + 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=ui.FULL_SCREEN, + v_if=("mounted", True), + ): + vtklocal.LocalView( + self.render_window, + ctx_name="view", + config=["wasm_conf"], + updated="local_rendering_ready++", + ) + + @pytest.fixture def ref_dir() -> Path: return Path(__file__).parent / "refs" @@ -21,3 +120,13 @@ def server(xprocess, server_path): # clean up whole process tree afterwards xprocess.getinfo(name).terminate() + + +@pytest.fixture +def utils(): + return Utils + + +@pytest.fixture +def ConeApp(): + return Cone diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index d6ef582..0000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -pytest -pytest-asyncio -pytest-playwright -trame-client[test] diff --git a/tests/test_cone.py b/tests/test_cone.py new file mode 100644 index 0000000..7679cb5 --- /dev/null +++ b/tests/test_cone.py @@ -0,0 +1,64 @@ +import asyncio +from pathlib import Path + +import pytest +from playwright.async_api import async_playwright, expect + +BASELINES = [ + Path(__file__).with_name("assets") / "cone" / name + for name in [ + "00_startup.png", + "01_update_resolution.png", + "02_unmount.png", + "03_remount.png", + ] +] + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "config", + [ + ("wasm32", "sync", "webgl"), + ("wasm32", "async", "webgl"), + ("wasm64", "sync", "webgl"), + ("wasm64", "async", "webgl"), + ("wasm32", "async", "webgpu"), + ("wasm64", "async", "webgpu"), + ], +) +async def test_cone(ConeApp, utils, config): + wasm_mode, wasm_exec, wasm_rendering = config + conf_key = f"{wasm_mode}-{wasm_exec}-{wasm_rendering}" + app = ConeApp(f"cone-{conf_key}", wasm_mode, wasm_exec, wasm_rendering) + task = app.server.start(exec_mode="task", port=0) + await app.server.ready + RESULT_BASE = Path(__file__).with_name("results") / "cone" / conf_key + + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + page = await browser.new_page() + await page.set_viewport_size({"width": 300, "height": 300}) + + await page.goto(f"http://localhost:{app.server.port}/") + await expect(page.locator(".readyCount")).to_have_text("1") + await utils.assert_screenshot(page, BASELINES[0], RESULT_BASE, threshold=0.1) + + app.resolution = 60 + await expect(page.locator(".readyCount")).to_have_text("2") + await utils.assert_screenshot(page, BASELINES[1], RESULT_BASE, threshold=0.1) + + app.mounted = False + app.resolution = 4 + await utils.assert_screenshot(page, BASELINES[2], RESULT_BASE, threshold=0.1) + + app.mounted = True + await asyncio.sleep(0.1) # Debounced resize needs complete + await expect(page.locator(".readyCount")).to_have_text("3") + await utils.assert_screenshot(page, BASELINES[3], RESULT_BASE, threshold=0.1) + + # Clean up resource + await browser.close() + + await app.server.stop() + await task From a4f3785d1eba6a2ea99da97ea31e65f7eb37c138 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:23:15 -0600 Subject: [PATCH 2/7] ci: build js for testing --- .github/workflows/test_and_release.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_and_release.yml b/.github/workflows/test_and_release.yml index f5ee4a3..aa5a0f8 100644 --- a/.github/workflows/test_and_release.yml +++ b/.github/workflows/test_and_release.yml @@ -57,6 +57,17 @@ jobs: version: "0.11.24" enable-cache: true + - name: Set Up Node + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Build Vue Components + run: | + cd vue-components + npm i + npm run build + - name: Install the project run: uv sync --all-extras --dev @@ -82,9 +93,8 @@ jobs: with: name: pytest-results-${{ matrix.config.name }} path: | - tests/refs/*.yml - tests/refs/**/*.png - assets/** + tests/results/**/*.png + retention-days: 1 test-npm-build: From 0af7ee2846a7b200dfda58653351d0b432808e24 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:33:59 -0600 Subject: [PATCH 3/7] test: capture all screenshot --- tests/conftest.py | 4 ++-- tests/test_cone.py | 27 +++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2300e1c..511595c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ class Utils: @staticmethod - async def assert_screenshot(page, baseline_image, result_directory, threshold=0.1): + async def compare_screenshot(page, baseline_image, result_directory, threshold=0.1): test_image = result_directory / baseline_image.name await page.screenshot(path=test_image) @@ -29,7 +29,7 @@ async def assert_screenshot(page, baseline_image, result_directory, threshold=0. file_diff = (test_image.parent / test_image.stem).with_suffix(".diff.png") mismatch = pixelmatch(img_ref, img_test, img_diff, threshold=threshold) img_diff.save(file_diff) - assert mismatch < threshold + return mismatch < threshold def create_vtk_pipeline(): diff --git a/tests/test_cone.py b/tests/test_cone.py index 7679cb5..8aa007f 100644 --- a/tests/test_cone.py +++ b/tests/test_cone.py @@ -34,6 +34,7 @@ async def test_cone(ConeApp, utils, config): task = app.server.start(exec_mode="task", port=0) await app.server.ready RESULT_BASE = Path(__file__).with_name("results") / "cone" / conf_key + valid_image_comparisons = [] async with async_playwright() as p: browser = await p.chromium.launch(headless=True) @@ -42,20 +43,38 @@ async def test_cone(ConeApp, utils, config): await page.goto(f"http://localhost:{app.server.port}/") await expect(page.locator(".readyCount")).to_have_text("1") - await utils.assert_screenshot(page, BASELINES[0], RESULT_BASE, threshold=0.1) + valid_image_comparisons.append( + await utils.compare_screenshot( + page, BASELINES[0], RESULT_BASE, threshold=0.1 + ) + ) app.resolution = 60 await expect(page.locator(".readyCount")).to_have_text("2") - await utils.assert_screenshot(page, BASELINES[1], RESULT_BASE, threshold=0.1) + valid_image_comparisons.append( + await utils.compare_screenshot( + page, BASELINES[1], RESULT_BASE, threshold=0.1 + ) + ) app.mounted = False app.resolution = 4 - await utils.assert_screenshot(page, BASELINES[2], RESULT_BASE, threshold=0.1) + valid_image_comparisons.append( + await utils.compare_screenshot( + page, BASELINES[2], RESULT_BASE, threshold=0.1 + ) + ) app.mounted = True await asyncio.sleep(0.1) # Debounced resize needs complete await expect(page.locator(".readyCount")).to_have_text("3") - await utils.assert_screenshot(page, BASELINES[3], RESULT_BASE, threshold=0.1) + valid_image_comparisons.append( + await utils.compare_screenshot( + page, BASELINES[3], RESULT_BASE, threshold=0.1 + ) + ) + + assert all(valid_image_comparisons), "Some images don't match" # Clean up resource await browser.close() From aa79b7b1d5f998ec0492f75b2ec06991fcea07e5 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:49:00 -0600 Subject: [PATCH 4/7] ci: add github baseline --- tests/assets/cone/00_startup_github.png | Bin 0 -> 2520 bytes .../cone/01_update_resolution_github.png | Bin 0 -> 6690 bytes tests/assets/cone/02_unmount_github.png | Bin 0 -> 1151 bytes tests/assets/cone/03_remount_github.png | Bin 0 -> 2417 bytes tests/conftest.py | 20 ++++++++++++------ tests/test_cone.py | 8 +++---- 6 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 tests/assets/cone/00_startup_github.png create mode 100644 tests/assets/cone/01_update_resolution_github.png create mode 100644 tests/assets/cone/02_unmount_github.png create mode 100644 tests/assets/cone/03_remount_github.png diff --git a/tests/assets/cone/00_startup_github.png b/tests/assets/cone/00_startup_github.png new file mode 100644 index 0000000000000000000000000000000000000000..e528a9eb4b72dab1ab0d58fbabc82ed9abffb661 GIT binary patch literal 2520 zcmdT`_fu1Q77vR;0C^ZdBZ?piA~Co`lp=^cF(DyT0|c-nQWgcJHvtJE7imfey&04$ z3WOp8(jKu;q%67+A|M+nc`gJnC>ZLy@wfdCc4y9=IdjhWl<%24-}xrl+v4SA56VIy z5P3@imIQ%F=!ie)-@wYMx(WgUk$Gr|J@0tCaAA^4hdZnM@eL&xpG`XQ4sqa+xAilv zTt~$c%0ZiG>9)8IMJnL`I46>G`{U{1T{ZbnPe*znWZ^G!wciwHE_ZiTjt^iKw!>(r zRtow3{0xDYRr7={{|vu>P^~>c^hhc(xKzSCY45#gHD1Os`qJd~Yk}Ezu|#(0i>;-u z3iX}&=_#{_@BjYxdPBapssha${N~*G{b7?`Vfz>)CF+&pqH0uB?j*lP&jzrv2C zy&cTgEt(X~$HnZlw^!|S7fnr_2@{U6XiM=(6M0N$1*{`vZeD6MSa0$3wSVWt%ceEEQUVF3;H7qFy~%HXG;kn{g*=EEb&2Yhj=l8jl>7bedGSjbJ6RI zldS{mgur-8Gx&p-V>&lRhCU57`AzzmQCq_Va{~nf$GPYlYzu5Lbbg0?pj7#J&UDE4 zn>!z)C(FnFP8Yz@G9P4oegstkabzPFv5uI8oVe+9o>-@sg>B{#=bTXF5;k=0**OA= z{8$JLA2DSSPc}AUo1LrSY9wo)hYA6lNFGlQS*c$G3^_QYk*u+RH^5@lfuF0B%L&9Q`SiWYCjHe=xdD4bk|srsmQ!3>CJM1qInCX z{^z7Unrz%YwNm8^i5Tm;m1oLuEjB_K^bbd}>oW~l#6Sszj`JDNLtz#=IVdX_^x#1z zvpvK3BU;)7l&vk0wA=4UnUMyJjPR61y|o-u5+~j)BN?>YKz5;XnNdEJYI%43`>v=ibo}<;e3r)HOFx<#koI@ zIHwmwlbUwgE=4CJN_B=>m!BHpoRu?YBj9TLs9uR_L5=#9mY}kYS8~E1obl&x3$n_z z&4b3|gTf_6ydQ@`OK;c_+;!zR6KIW=My` z+<&Tf1D;l(#Ji-kmx8Ay9A)g-XVdNXV;-ULcblunsF0xaQgsHP+9zJA4bAY*> zdv6C)6=Xw$b62@_!!w4`uxzwXfgypm0&@#wR}83$25bPF_KCLC&1?-eCjQonRIk4! z;)>%%vDM2=Xpa4ym{l85Xq61=q#R|Vvdlnb)-J>Zd7;6hq?e_NP(=-khdv}O?-kzX zFsOOh+jm361n4}dwC3cTd^%Nx{TaXT(Tl!W?-B(ZH8U~D-wbl~07Aurx+VK?E?hA~ z+i`mBA}I5|h7CnC4Wee86Nrc_cDE0UMl{2Lo4Mjj(H&R~2K4Kbg=9C1H}(8-3vBfJ z!q^Sr@AlB#ZC^3(i-{o z3qAiw-Fme{5{zj~o%nLrP*m;u#DThDBff3DcF*9Sz+O(}jNrA+jAxlK6CEpmHl8xE zc{tNe!RM2aKCk-}*{nQ$x_dR*n8{H8*avgdfZrE-{B{Phr-(4yedj|P4&j5yQV#}iaG_G= zri(>BG^Y^kq$^G+Ev}1N)lZ)aUg{R=wYvt4zWwKa@R^>hwZiG!7PL3S{k#|5r-UWD zdJ%dh%<5rJ4dZV@974h9$rMUXzn%9ox$%%At!bo z!G*{iYkIXn|L35b!Xw7TQEKA23A1jU;rwNaIC&Uc!-67yOl{~&XJAL{%&+ux8g(>I y<>`H>M(+PxjKxSmAUiu!`Aq=$K9dqZ%p@T`Srkh>75XMvfmq^fu{Gw`?)?SytBE%N literal 0 HcmV?d00001 diff --git a/tests/assets/cone/01_update_resolution_github.png b/tests/assets/cone/01_update_resolution_github.png new file mode 100644 index 0000000000000000000000000000000000000000..84b926b91807e0c4ebf41860cc054f53eecea064 GIT binary patch literal 6690 zcmd^k`8$+f`2X0ACHoRG@9ZW+A+nCfHi$7yS+W(PY}q1fG^okgMr2>cGPY!k$`U1G z%aCF$iO`Up!Z3WM@AdsBKG)}m=enMApZmGbdEKw)yw17q^VHhPl$%400{{SUo5782 z000K1e-Aq&-SQW7Rtf;%1(+FKMTO_D6_5j*CZhXdXB0OwmHJB+6klr)pUcYbR44_F zjw_CFD2@%orv()pMZG1bPK!sPDefSuQ!)I=rqy!36g%2m7gzD*g5iA+$jg=&8P_oU ziciaAYz&wCjsy^AHiI_a3=V1Sf8J+WTo@|QF=Tm>8aw?)ZB{^jP73fyM;i$EAqZ!G zpL`GZ-#h z>f-h+V(wgFcvV1nJpIG+cMa{~zi_dVlM#>NbFDxBJ+onuD){IdsbgpBGcGA?bMC;E zww8jQTiFn&W7u7J?<@#tV-as-xX&fS{QgoJFQCr=29RI^{;xC4qOQIb+l}%K!SVsV zR^H~{kZ99GYWVE*^z>ZQ z)UHQv)}|7~)^776gS91CdLDi{aN@_=PFY=@YV_npF}IYc@^^+5d-=A7g@(3_dh?=~ zFPDn(E33>ztZcDAo0yv$8Q6{=gvDcug;9n*U;0egsD&>bbpQI@zn?bI06xPL;^&a( zr<)&#Ddwj;Xu5p25+6`BGS)XwsYYd9MloM6VdUSK`WAUVEEa(ndH)^&vSS)=0-Gja zic3)j{~{*74(WHye2gi!qPOeMi31Dh!K@CH&+i)}x{h^KLdeVn_SLve{>Hc?`M%qp z0!CWOB1|jA)*X{NJ?(6G>hr}M!%$MK4*sIOmX^N2$Es@${sFywd_WS5{%3hyb zQ9Kr7qrCd~{SQVdmhL}Q8{(gT7{mIs}f<WddbIasH}96aLfyi=if%b{IMsi;;uG4#*meiJB_0z zy_DqubMJ=LC8?{%*8GL$;fo{@t8+zM1#4Wq;totMl~dklMR7snlfpF)y)Qo?LQp?aXyb(K8REi(_l`Le$O zL*P}*a|HICc|3YXEPD5*>->mb>=TDd35`4SepVC16tR1&6x$OMJcH0xu)g`Ey~{zJ zTBXNugkbX}Th2;1xABphQ7OL};>biun3AC}Z;vU^RX6Cz-rz`rRibZm4g&4s@2XBu zI|VDY9F#s`&1bZY?D?VsjrBN9Df8+zG2!T{oxhZXWfkIX<@}9-8N0nV1;*0ERs83) zUqs{x#z0<;>R(JmaVc^LT~#VHfr$f!20lfVTYYsGF4yg{j+Qt=1vGc1AZ+Z znr1EEiv7#|b^K}9ytuXd^)k0Y2BB$TknHjI&8P$@<@m6IanXNqQWJIEZLy2LW%T}T zCl-VK`pLYHJx+r@cGuCmo$M0M+7^Sg(cYOlzK$#rnG(c;(Vtj9O)|Js-ihMz#uZ(` zfrGe_;@dhu3edV$r?ak+1RZjfejy>5 z2xSPQsuCv8#uVPr)<#5Z-{e+SQc{k&vhQ(pyU(Ao<-5|>u^Ewx{1ZxZGgCc9Z#)S{!S zyOmgo5tkknIopHoDBXU6gd7(Ln6G8D9Jjja$1iCs-ZJ`jh;}+{H1u19n}~a&;o^

K0{gpZketFFP(uTReGlyJbuH9IYduOIVN0M>c+ zRHpKJG#GUffZ?)AE55G7jdDxXS9*$!mqvr{&3YtN**yk}9ld@{j;|6I$2Q)IqpN{E_W z5IH26JiA)QE>tj|jaGUrd97@~MhH3BtfUD~+_~N;Jm!o4-h(7mhFJV5L-~%g(T+2R z)QU?lv|E|Jse|POw}10-@K56c-o@2dY!HDg2F}J~Vs)jZQ6(n6^c}<**^o#KrGCh)VOtR#I+hU60K}V?Il?K>e_ODG^Vaw1&C;Wm6ZEP0P;-!We%(4vWb5%kWE*8zZ#>GaFIhsU@QADn36=lqJh=Sv zYsI5v$T>Vj;vrmIW7W`@V4qg~4V`lwa=}ZjS@67??8_>hh3iuLDnCa-TJ{d zO$3q&`vIt3jYd;-N42V`>VALG9u^QmU4BlMBoMaXJ)5-d zJd}vjp@Y#Ngt3XTgb1g9|uS>fQSC`Bno38-r?(=YAecAeQ?-Lw+e96ePhqZSh&J0S9`q#p#2YVv^}oEWap^WWJ}6%6F$c2Qj=O z6Y!>~EX>mlC9{_sB#uH|n@w4Z zc_7-tMO%y`5c|RMFKbKHegCeJgSUQk|5Q`^HBtmU>c0)Sjz(WD!!MD8Za{UUekufJ z#E+vw!?KvJ=q$Y_wk*^H2a6~>g`du>i|?~mnHa#fGE?OBj>JrF|?yK>E=UCu`)U)18y& zgGJLP&H-`g;;({8yQOBGu#H8>?V+6V;9s9<>pEW0wu47t3weegdJ#c`6do;Gy;rAz z>t~H%BpWX0B8p=HQvBnR3AVhv{9V|l%iEBZf>Y`uzGe^=_}_VAYB_Q_`}ewS6>U5| zUT?1P((|N)8O4s+(~|<7u(C^Pm!SqN0RH$;j@3-+XXKYX&hfJ=37F9K@Pn=MEM1sx zhOO@%k(vSD+hb_d*yDp`x=JH0xn$t>(9+K`+yIMHdnv;&?Wf!y75PcjHW@f=}POGH#=y{_l}!4)X< z^zc`aYiHEq+ETAUSH#AzL^P!5LIKy}Max*fjG$(%nf|k~k}R$BgI5X}_pj_VB-z!w z&_)m6{Lqr-RMlR+cS@$eGa;|-u>n_gcN&$Iun+| zt>G7Yf2G@eE_fX2MMFQxeg$R#gL*B$%z>|KVK~FexJ6MU+dfnAS1}S)jg^Ugt`@dG z>94+PpH&!Dr`3Emo&x}Yga2v)dVZPG!*9P5ML2$w<&0-=q&}ZB(NVq#8Z$_?*0j@3 zxe9v{AK+h=&ughFbbec5LDJ6%Dha)RikrRZLnXLkYYFG4%iwrp(ln&3yrB7fq0*I8k0OUIkZb!~>$)+;%ka9a?=ZYh(A>`I z)k+oRMK8V$ZA}`j@~d z(hvY9#@5#M*Gt{_7%jRue=l*anF_PA5%Mk!f_=L$UxqO?*@s^^R8Zcl>b?DbZ%=(9 zhNPkyV+vk)}*syI2Y@V;Mkx;)6Yz>~L?$HWr7# z#`Q30{i*y|Sl3t_8gUmJv`SkyWk>opTAEv3Q5;(pH{WfGPHT#^rPde+lp`^QRL_Fa=YMNsr69S1 zzP8Z2A@}8JXzCaYE{hCFut-BS2ZLs)LY0qxFs2BE^#q+A60;- zn|L1;$-@7_27oMd^s1hD@#T@D4>)Df`k^wEUuR>?h($+b+Lh$k){pn)7kN|1#2_%6 ze$%@psl`Rv&6}?}YYQ<}DZV%*F-SaFZ%TryknyludT$r2k@4zdAtq(%d&8tiEtM>lKjqVz;5zvLyOr^Q%Y+x`~C?t7smhuOnmoxZp=j9%mY)Z~&>4X?uHTg9tU z{9B1{jeF!0bk4Vid#p>!w%%0m|Ez4k+B&vB<0MjA6gGafXE{M<;O4TT>Kjv_EXa6y z4W_;;Q@4eG60=zRi)C!v;@=;nk^NjB#-9Fe$aYd)D*?5yknzCAiGm-F7I84>GPlOU zCzJVLnNnmq2Te!jrbxkwz>?wCE9`Hvr25i>3%6`>*Nnf>iC+`2GWTgsyR?^{^^kwB zhVQ!>fx0q`cgvCY2ujTe%UTgJn#IGH%}DhR!sX}5MNZHNVzug!dAF}Eao)wI#@5;3 zTL}UxFosyB(~TLAvUkOVXO@pjm1RYFa7CAd{1;*}Jo6CS5Xrq|)sw0`3l;@h^lS7- zXy&ApM+%BiiBh{V5CE)e${Gi^&kE1fo0oY;Cu@YPsJtsfnXo!&@xwm6|NC+5QBqfP zdt0q9F6fP^@Vc=1izd!dl86|Lk^rJzgZgUM2z@v1>6{1dvdCggS!UE)ZxThR3g>sr z(4Cy;sb6#NmEmRSTJg=~nVannGN>;>Nfj|wJJ-|02r6;y*tUYT1lJvWZ{XFU)7}x8 zNeV1!BbkzTm=8pO*-Qi()pfZq4Iy+URo;$XdlX|ZUxh}!i0YLjw`eDflS~71yeKkz zO#0>r+ovNEC<|RKB3hYa5MPDA)Lm(^PcpckJmk^TUYmOZWgSX571}#4KKS0_; zlV{_y(=R@7ag~`bQ=+FMuL*bQ|6`3~jzXAnzBP52f@Es$^%f?@yypkWE4pg)9TW!2T;{Ze~K$7Qp?1dwqeU3Cm_+jc#a0HLWQebqaK z%(O>{@Y!=6K|l4g6Ux#0=AU&vzvM))(+r+%JN8)EZ6EJ zLG3#|Vca4w)DZD8#T?;~`K^~YGemi!J_h%tcy0!_ejyslMl<-mP5Vq8smT7jCP5l0 zTNTksgMUqZ%MOsI7aaer7^h!!r7tU<@YGHIlcS?wo#8(+yY6s}F^Ita^#Fp|ADPwH9p%#o(hT$niD;34+7*5n)LI_ATXRh$;AJEoD~*tX z*8KMAT|i9|iDx_!Ow*s|IsG*yUcYYZ_Yb3R2sa|X3`5e?-QG?}?PIf3Bn2$bPd+Zh z*UbK8Hh5k&sq$y1ZDC;z;y7pan8J1s_9sSJhuR_nK;>F70vnp?9C@-5&==jFZ6 zD}R0R>hA8her-VUM62kv;h$gbKm6oPO=(@#{@Zs~2^=f>w0+OfM>ijQ*nae634>eF zr$;3>?%$P9dw$wj^iV{c%Z~m3YQ7fii_|yaSo~A|;lp=Dd-u)DEf-Ip%qVgCvhBmV z?UC_*+wbd#3$+CPz1`CO`|h{D%TNA#b@@{X!?DsYzmHGVc)l|Np|y6Bh#+Y@qUknZZND|J}Ru69R!E44$rjF6*2UngGIppziVPkLUHeuGi=NdVk;V&-e5B{BT{LTfcY_ zwrn!o1c5-dkch5i2t;GA`h{wO%y&~b0s_&oAh{m#jmw)F_GIP9!kgw2cZQ~1ZA5H& zDzLMmJldE+6KS0gmNsV0#A($3K=t*@$&QhAZD?@#;tFrgV%$QBqB8Nrp=(Eo;e}3w@hTZipL7TB{_r}3IWzs}c!)Q`W)GBwv zRCdUDHNztWt&B-Zda^-lE;wQ$dH!2X&BA+J;_QRP%8|K}$H;=c;bYw~M zYe>_z%aM^|?QInY7eC$8-O>8TTJ8eN6i1Jd6;2N(kE~9udEl1Dr4*X>;}JhJp|7{s zE8nSMA-~UbRW_=pT4Cw#Xk3=Aydo^(MpOw{j@ON;>qn+uIK-)DI#w%W!-{WjOUgsu z(d)oco#LZrCB?Nf5vp+6TF9*u~w13dQJv&XU z$F7Rg(9THwWa+B8^HLEERT{=PFjzafJaAlB^%90E4r6dMk!9`PDqkjyY$s|7?;_Ef zR0qg>Mk(MmnFE*Hg^`3|jFk^x^7zDnJ!Z~f_qZgcxi3cKCp=wH{c)c=lTXHoYDW`d z-$g-LyYY7{;Syz8`!TZl=?$m@7AJ!*-a#PADA5bm6Tf7&2UF}0=&t9% z_*5a!J%bkWn{BM{*;ftoIk5Or4Dk7Z+k`p(rQjHYBIt4=v6F%k$70uS(`Q!BSlM!5 z1mQbQ<^;SvT z32-R=d`&>Od_~sA4m!Wd)vE5%%k)cJ=Tg4yQnk4*7pegLznQ<*6sK*D8@A z=;GW7avCr*9()}@<@*!ALvxijqSwSK#uN|`d><*6cH?x)F$dW}0VQVDt@BF zR!K74t_-+_n%FR&yV`;S093#<5! z+4Z2X?J#pgvk{MCp@$OsenNr(juH~Pq2(AFsp=XDyu}O$ z5bpsAE1%migZqo5e(y|}L8gt%htOEEGdmqv9$K*{UiN~6)@oO#ZE!khP2|U=rvo`| zr}l`8-3@pYK|EQR6QizE2Q}kiz%w;7)<<2{0WImB%|7jm<{;<^^=a4VQBnaxwzX(D zKN9H9*egB_dd42I`PtSk+Tq5%|6o2t{G1VeH&rdA-5s;z20a3sYcHvSZO4BJ%-Bw| z5HIY{Xi$FDGs)|`=P^-=p3FUCSX}F@<-|3T4cE`jbO?WU1q!fz$ixaHS$ zWJ5^|K%ZT#!|P z4_J<2!*XfaN?i{)FH(S)A7C!(oODu{pwBaI_vP$vh`*5DY5xJD=y>1eonXW!D!w47zVC)8?ZuC=oOT}gXBB+NRUp{78gm~+*-JzDmx4i3|*8qVVL;r$XvE*oApoU3L@R;^F{s)_*1x| z3mp=U+=V;AJIml~OI7U+R8(OL7`DwUN|}Kq@BtHzOZ7&JG(e?|Ihycb7cSKgBcd2r zw6HAg%%&FNJn4;A@N?9raJkvdJHY>gM<-X!P~A6P*WXZx0_dVVr>fr!jc&5sq|D`+(N!kkUu?Gxcp>_ zMv)+SnzT3n+W`Jj|GfXFm=zqjFR4_bw?la#&{A(q8z7m5p3@$e-qJw^Lc)8w3S9zk F{1*iW9ijjL literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py index 511595c..41a45d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,17 +19,23 @@ class Utils: @staticmethod async def compare_screenshot(page, baseline_image, result_directory, threshold=0.1): - test_image = result_directory / baseline_image.name + test_image = result_directory / baseline_image.with_suffix(".png").name await page.screenshot(path=test_image) img_test = Image.open(test_image) - img_ref = Image.open(baseline_image) - img_diff = Image.new("RGBA", img_ref.size) + img_diff = Image.new("RGBA", img_test.size) + mismatches = [] - file_diff = (test_image.parent / test_image.stem).with_suffix(".diff.png") - mismatch = pixelmatch(img_ref, img_test, img_diff, threshold=threshold) - img_diff.save(file_diff) - return mismatch < threshold + for ref_file in baseline_image.parent.glob(f"{baseline_image.name}*.png"): + img_ref = Image.open(ref_file) + + file_diff = (test_image.parent / ref_file.name).with_suffix("-diff.png") + mismatch = pixelmatch(img_ref, img_test, img_diff, threshold=threshold) + img_diff.save(file_diff) + file_diff.with_suffix(".txt").write_text(f"{mismatch}") + mismatches.append(mismatch) + + return min(mismatches) < threshold def create_vtk_pipeline(): diff --git a/tests/test_cone.py b/tests/test_cone.py index 8aa007f..4e380b2 100644 --- a/tests/test_cone.py +++ b/tests/test_cone.py @@ -7,10 +7,10 @@ BASELINES = [ Path(__file__).with_name("assets") / "cone" / name for name in [ - "00_startup.png", - "01_update_resolution.png", - "02_unmount.png", - "03_remount.png", + "00_startup", + "01_update_resolution", + "02_unmount", + "03_remount", ] ] From bdd8b574d6b0b1e07cbd7a2426a5b5918f4d1152 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:52:56 -0600 Subject: [PATCH 5/7] ci: add github baseline --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 41a45d1..96437e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,7 +29,7 @@ async def compare_screenshot(page, baseline_image, result_directory, threshold=0 for ref_file in baseline_image.parent.glob(f"{baseline_image.name}*.png"): img_ref = Image.open(ref_file) - file_diff = (test_image.parent / ref_file.name).with_suffix("-diff.png") + file_diff = (test_image.parent / ref_file.name).with_suffix(".diff.png") mismatch = pixelmatch(img_ref, img_test, img_diff, threshold=threshold) img_diff.save(file_diff) file_diff.with_suffix(".txt").write_text(f"{mismatch}") From 8349316f93688438f9aa4010aec3dcea2a008937 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:55:47 -0600 Subject: [PATCH 6/7] ci: push txt files --- .github/workflows/test_and_release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_and_release.yml b/.github/workflows/test_and_release.yml index aa5a0f8..7480d39 100644 --- a/.github/workflows/test_and_release.yml +++ b/.github/workflows/test_and_release.yml @@ -94,6 +94,7 @@ jobs: name: pytest-results-${{ matrix.config.name }} path: | tests/results/**/*.png + tests/results/**/*.txt retention-days: 1 From 49744580981320f9b45e97d90a7e23c1d19f9d95 Mon Sep 17 00:00:00 2001 From: Sebastien Jourdain Date: Fri, 26 Jun 2026 15:57:01 -0600 Subject: [PATCH 7/7] ci: rename artifact --- .github/workflows/test_and_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_release.yml b/.github/workflows/test_and_release.yml index 7480d39..f5d99a4 100644 --- a/.github/workflows/test_and_release.yml +++ b/.github/workflows/test_and_release.yml @@ -91,7 +91,7 @@ jobs: uses: actions/upload-artifact@v4 if: always() with: - name: pytest-results-${{ matrix.config.name }} + name: trame-vtklocal-tests-${{ matrix.config.name }} path: | tests/results/**/*.png tests/results/**/*.txt