Skip to content

Commit 07420ba

Browse files
authored
Merge pull request #360 from astrofrog/visual-testing
Add infrastructure for visual tests and first test(s)
2 parents 9d44ed5 + 286f513 commit 07420ba

File tree

7 files changed

+210
-7
lines changed

7 files changed

+210
-7
lines changed

.circleci/config.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,98 @@
11
version: 2.1
22

3+
jobs:
4+
5+
# The following job is to run any visual comparison test, and runs on any branch
6+
# or in any pull request. It will generate a summary page for each tox environment
7+
# being run which is accessible through the CircleCI artifacts.
8+
9+
visual:
10+
parameters:
11+
jobname:
12+
type: string
13+
docker:
14+
- image: cimg/python:3.11
15+
environment:
16+
TOXENV: << parameters.jobname >>
17+
steps:
18+
- checkout
19+
- run:
20+
name: Install dependencies
21+
command: |
22+
sudo apt update
23+
sudo apt install libatk1.0-0 libatk-bridge2.0-0 libcups2 libxkbcommon0 libatspi2.0-0 libxdamage1 libgbm1 libpango-1.0-0 libcairo2 libasound2
24+
pip install pip tox --upgrade
25+
- run:
26+
name: Run tests
27+
command: tox -v
28+
- store_artifacts:
29+
path: results
30+
- run:
31+
name: "Image comparison page is available at: "
32+
command: echo "${CIRCLE_BUILD_URL}/artifacts/${CIRCLE_NODE_INDEX}/results/fig_comparison.html"
33+
34+
# The following job runs only on main - and its main purpose is to update the
35+
# reference images in the glue-jupyter-visual-tests repository. This job needs
36+
# a deploy key. To produce this, go to the glue-jupyter-visual-tests
37+
# repository settings and go to SSH keys, then add your public SSH key.
38+
deploy-reference-images:
39+
parameters:
40+
jobname:
41+
type: string
42+
docker:
43+
- image: cimg/python:3.9
44+
environment:
45+
TOXENV: << parameters.jobname >>
46+
steps:
47+
- checkout
48+
- run:
49+
name: Install dependencies
50+
command: |
51+
sudo apt update
52+
sudo apt install libatk1.0-0 libatk-bridge2.0-0 libcups2 libxkbcommon0 libatspi2.0-0 libxdamage1 libgbm1 libpango-1.0-0 libcairo2 libasound2
53+
pip install pip tox --upgrade
54+
- run: ssh-add -D
55+
- add_ssh_keys:
56+
fingerprints: "be:23:bb:43:77:fd:bc:2d:38:82:3e:38:06:27:0f:fe"
57+
- run: ssh-keyscan github.com >> ~/.ssh/known_hosts
58+
- run: git config --global user.email "glue@circleci" && git config --global user.name "Glue Circle CI"
59+
- run: git clone [email protected]:glue-viz/glue-jupyter-visual-tests.git --depth 1 ~/glue-jupyter-visual-tests/
60+
- run:
61+
name: Generate reference images
62+
command: tox -v -- --mpl-generate-path=/home/circleci/glue-jupyter-visual-tests/images/$TOXENV
63+
- run: |
64+
cd ~/glue-jupyter-visual-tests/
65+
git pull
66+
git status
67+
git add .
68+
git commit -m "Update reference images from ${CIRCLE_BRANCH}" || echo "No changes to reference images to deploy"
69+
git push
70+
71+
workflows:
72+
version: 2
73+
74+
visual-tests:
75+
jobs:
76+
- visual:
77+
name: << matrix.jobname >>
78+
matrix:
79+
parameters:
80+
jobname:
81+
- "py311-test-visual"
82+
83+
- deploy-reference-images:
84+
name: baseline-<< matrix.jobname >>
85+
matrix:
86+
parameters:
87+
jobname:
88+
- "py311-test-visual"
89+
requires:
90+
- << matrix.jobname >>
91+
filters:
92+
branches:
93+
only:
94+
- main
95+
96+
notify:
97+
webhooks:
98+
- url: https://giles.cadair.dev/circleci

glue_jupyter/app.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@
2626
SUBSET_MODES = {'new': NewMode, 'replace': ReplaceMode, 'and': AndMode,
2727
'or': OrMode, 'xor': XorMode, 'not': AndNotMode}
2828

29-
for name in ['glue-float-field', 'glue-throttled-slider']:
30-
file = f'{name.replace("-", "_")}.vue'
31-
ipyvue.register_component_from_file(
32-
None, name, os.path.join(os.path.dirname(__file__), 'widgets', file))
33-
3429

3530
def is_bool(value):
3631
return isinstance(value, bool)
@@ -59,6 +54,11 @@ def __init__(self, data_collection=None, session=None, settings=None):
5954

6055
super(JupyterApplication, self).__init__(data_collection=data_collection, session=session)
6156

57+
for name in ['glue-float-field', 'glue-throttled-slider']:
58+
file = f'{name.replace("-", "_")}.vue'
59+
ipyvue.register_component_from_file(
60+
None, name, os.path.join(os.path.dirname(__file__), 'widgets', file))
61+
6262
try:
6363
from glue.main import load_plugins
6464
load_plugins()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
from glue_jupyter import jglue
5+
from glue_jupyter.tests.helpers import visual_widget_test
6+
7+
8+
@visual_widget_test
9+
def test_visual_scatter2d(
10+
tmp_path,
11+
page_session,
12+
solara_test,
13+
):
14+
15+
np.random.seed(12345)
16+
x = np.random.normal(3, 1, 1000)
17+
y = np.random.normal(2, 1.5, 1000)
18+
c = np.hypot(x - 3, y - 2)
19+
s = (x - 3)
20+
21+
app = jglue()
22+
data = app.add_data(a={"x": x, "y": y, "c": c, "s": s})[0]
23+
scatter = app.scatter2d(show=False)
24+
scatter.state.layers[0].cmap_mode = 'Linear'
25+
scatter.state.layers[0].cmap_att = data.id['c']
26+
scatter.state.layers[0].cmap = plt.cm.viridis
27+
scatter.state.layers[0].size_mode = 'Linear'
28+
scatter.state.layers[0].size_att = data.id['s']
29+
figure = scatter.figure_widget
30+
figure.layout = {"width": "400px", "height": "250px"}
31+
return figure

glue_jupyter/tests/helpers.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from functools import wraps
2+
3+
import pytest
4+
from IPython.display import display
5+
6+
try:
7+
import solara # noqa
8+
import playwright # noqa
9+
import pytest_mpl # noqa
10+
import pytest_playwright # noqa
11+
except ImportError:
12+
HAS_VISUAL_TEST_DEPS = False
13+
else:
14+
HAS_VISUAL_TEST_DEPS = True
15+
16+
__all__ = ['visual_widget_test']
17+
18+
19+
class DummyFigure:
20+
21+
def __init__(self, png_bytes):
22+
self._png_bytes = png_bytes
23+
24+
def savefig(self, filename_or_fileobj, *args, **kwargs):
25+
if isinstance(filename_or_fileobj, str):
26+
with open(filename_or_fileobj, 'wb') as f:
27+
f.write(self._png_bytes)
28+
else:
29+
filename_or_fileobj.write(self._png_bytes)
30+
31+
32+
def visual_widget_test(*args, **kwargs):
33+
34+
tolerance = kwargs.pop("tolerance", 0)
35+
36+
def decorator(test_function):
37+
@pytest.mark.skipif("not HAS_VISUAL_TEST_DEPS")
38+
@pytest.mark.mpl_image_compare(
39+
tolerance=tolerance, **kwargs
40+
)
41+
@wraps(test_function)
42+
def test_wrapper(tmp_path, page_session, *args, **kwargs):
43+
layout = test_function(tmp_path, page_session, *args, **kwargs)
44+
45+
layout.add_class("test-viewer")
46+
47+
display(layout)
48+
49+
viewer = page_session.locator(".test-viewer")
50+
viewer.wait_for()
51+
52+
screenshot = viewer.screenshot()
53+
54+
return DummyFigure(screenshot)
55+
56+
return test_wrapper
57+
58+
# If the decorator was used without any arguments, the only positional
59+
# argument will be the test to decorate so we do the following:
60+
if len(args) == 1:
61+
return decorator(*args)
62+
63+
return decorator
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"glue_jupyter.bqplot.scatter.tests.test_visual.test_visual_scatter2d[chromium]": "3fe576be80889cc20063dd9e17f39899b2e40fb9a779eaa02e19b01181b332aa"
3+
}

setup.cfg

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ test =
3434
runipy
3535
nbconvert>=6.4.5
3636
glue-core!=1.2.4; python_version == '3.10'
37+
visualtest =
38+
playwright
39+
pytest-playwright
40+
pytest-mpl
41+
solara[pytest]
3742
docs =
3843
sphinx
3944
sphinx-automodapi
@@ -58,7 +63,7 @@ glue_jupyter.icons = *.svg
5863
# -Wignore: See https://github.com/glue-viz/glue-jupyter/issues/237
5964
# -s: Disable stdout capturing
6065
filterwarnings =
61-
error::DeprecationWarning
66+
# error::DeprecationWarning
6267
ignore:the imp module is deprecated:DeprecationWarning:glue.config.*:
6368
ignore:`np.float` is a deprecated alias:DeprecationWarning:glue.*:
6469
# possibly more serious issue with overlapping memory in glue/utils/array.py:30: unbroadcast

tox.ini

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ passenv =
1111
HOME
1212
setenv =
1313
JUPYTER_PLATFORM_DIRS=1
14+
SOLARA_TEST_HOST=0.0.0.0
1415
whitelist_externals =
1516
find
1617
rm
@@ -23,13 +24,17 @@ deps =
2324
notebooks: astroquery
2425
notebooks: pyyaml
2526
devdeps: git+https://github.com/glue-viz/glue
27+
visual: git+https://github.com/astrofrog/solara@test-host
2628
extras =
2729
test: test
2830
notebooks: test
2931
docs: docs
32+
visual: visualtest
3033
commands =
3134
test: pip freeze
32-
test: pytest --pyargs glue_jupyter --cov glue_jupyter {posargs}
35+
test-!visual: pytest --pyargs glue_jupyter --cov glue_jupyter {posargs}
36+
test-visual: playwright install chromium
37+
test-visual: pytest --show-capture=no --pyargs glue_jupyter {posargs} --mpl -m mpl_image_compare --mpl --mpl-generate-summary=html --mpl-results-path={toxinidir}/results --mpl-hash-library={toxinidir}/glue_jupyter/tests/images/{envname}.json --mpl-baseline-path=https://raw.githubusercontent.com/glue-viz/glue-jupyter-visual-tests/main/images/{envname}/
3338
notebooks: python .validate-notebooks.py
3439
docs: sphinx-build -n -b html -d _build/doctrees . _build/html
3540

0 commit comments

Comments
 (0)