From 8dd068eb51edf2249978c765bb4d67784278b465 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 2 Aug 2022 22:23:03 -0400 Subject: [PATCH 01/10] Add general test for WebAgg backend --- .appveyor.yml | 2 + .github/workflows/tests.yml | 3 + azure-pipelines.yml | 3 + environment.yml | 2 + .../test_backend_webagg/chromium.png | Bin 0 -> 7382 bytes .../test_backend_webagg/firefox.png | Bin 0 -> 12942 bytes .../test_backend_webagg/webkit.png | Bin 0 -> 6500 bytes lib/matplotlib/tests/test_backend_webagg.py | 61 +++++++++++++++++- requirements/testing/all.txt | 1 + 9 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_backend_webagg/chromium.png create mode 100644 lib/matplotlib/tests/baseline_images/test_backend_webagg/firefox.png create mode 100644 lib/matplotlib/tests/baseline_images/test_backend_webagg/webkit.png diff --git a/.appveyor.yml b/.appveyor.yml index 87f6cbde6384..e8b6c720f29c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -62,6 +62,8 @@ install: - activate mpl-dev - conda install -c conda-forge pywin32 - echo %PYTHON_VERSION% %TARGET_ARCH% + # Install browsers for testing + - playwright install --with-deps # Show the installed packages + versions - conda list diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8875a38cc1bb..72936870b803 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -281,6 +281,9 @@ jobs: --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ --upgrade --only-binary=:all: numpy pandas + - name: Install browsers for testing + run: playwright install --with-deps + - name: Install Matplotlib run: | ccache -s diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4c50c543846a..14bcc7125d84 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -163,6 +163,9 @@ stages: --verbose --editable .[dev] displayName: "Install self" + - bash: playwright install --with-deps + displayName: 'Install browsers for testing' + - script: env displayName: 'print env' diff --git a/environment.yml b/environment.yml index 2930ccf17e83..edc4e9cc9946 100644 --- a/environment.yml +++ b/environment.yml @@ -56,6 +56,8 @@ dependencies: - nbconvert[execute]!=6.0.0,!=6.0.1,!=7.3.0,!=7.3.1 - nbformat!=5.0.0,!=5.0.1 - pandas!=0.25.0 + - pip: + - pytest-playwright - psutil - pre-commit - pydocstyle>=5.1.0 diff --git a/lib/matplotlib/tests/baseline_images/test_backend_webagg/chromium.png b/lib/matplotlib/tests/baseline_images/test_backend_webagg/chromium.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1f0a89a631cc33a32eccde56015316d9dff47d GIT binary patch literal 7382 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 U>GYF=gFpy(_QILU(;nac2Ms>?r2qf` literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_backend_webagg/firefox.png b/lib/matplotlib/tests/baseline_images/test_backend_webagg/firefox.png new file mode 100644 index 0000000000000000000000000000000000000000..0ba9183c61a032d1db3dc4c7cc52e3c7c002cb60 GIT binary patch literal 12942 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_backend_webagg/webkit.png b/lib/matplotlib/tests/baseline_images/test_backend_webagg/webkit.png new file mode 100644 index 0000000000000000000000000000000000000000..08f512fb9ebeb24f4883c7ccbe4aca9cac7824f9 GIT binary patch literal 6500 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 Date: Tue, 2 Aug 2022 23:11:01 -0400 Subject: [PATCH 02/10] WebAgg: Add test for the toolbar --- lib/matplotlib/tests/test_backend_webagg.py | 68 +++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 527553f4912d..95b2dcea2119 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -2,6 +2,7 @@ import shutil import subprocess import sys +import warnings import pytest @@ -71,11 +72,6 @@ def test_webagg_general(page): # Check title. expect(page.locator('div.ui-dialog-title')).to_have_text('Figure 1') - # Check toolbar buttons. - assert page.locator('button.mpl-widget').count() == len([ - name for name, *_ in fig.canvas.manager.ToolbarCls.toolitems - if name is not None]) - # Check canvas actually contains something. baseline_dir, result_dir = _image_directories(test_webagg_general) browser = page.context.browser.browser_type.name @@ -89,3 +85,65 @@ def test_webagg_general(page): err = compare_images(expected, actual, tol=0) if err: raise ImageComparisonFailure(err) + + +@pytest.mark.backend('webagg') +@pytest.mark.parametrize('toolbar', ['toolbar2', 'toolmanager']) +def test_webagg_toolbar(page, toolbar): + from playwright.sync_api import expect + + # Listen for all console logs. + page.on('console', lambda msg: print(f'CONSOLE: {msg.text}')) + + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', message='Treat the new Tool classes', + category=UserWarning) + plt.rcParams['toolbar'] = toolbar + + fig, ax = plt.subplots(facecolor='w') + + # Don't start the Tornado event loop, but use the existing event loop + # started by the `page` fixture. + WebAggApplication.initialize() + WebAggApplication.started = True + + page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') + + expect(page.locator('button.mpl-widget')).to_have_count( + len([ + name for name, *_ in fig.canvas.manager.ToolbarCls.toolitems + if name is not None])) + + home = page.locator('button.mpl-widget').nth(0) + expect(home).to_be_visible() + + back = page.locator('button.mpl-widget').nth(1) + expect(back).to_be_visible() + forward = page.locator('button.mpl-widget').nth(2) + expect(forward).to_be_visible() + if toolbar == 'toolbar2': + # ToolManager doesn't implement history button disabling. + # https://github.com/matplotlib/matplotlib/issues/17979 + expect(back).to_be_disabled() + expect(forward).to_be_disabled() + + pan = page.locator('button.mpl-widget').nth(3) + expect(pan).to_be_visible() + zoom = page.locator('button.mpl-widget').nth(4) + expect(zoom).to_be_visible() + + save = page.locator('button.mpl-widget').nth(5) + expect(save).to_be_visible() + format_dropdown = page.locator('select.mpl-widget') + expect(format_dropdown).to_be_visible() + + if toolbar == 'toolmanager': + # Location in status bar is not supported by ToolManager. + return + + ax.set_position([0, 0, 1, 1]) + bbox = page.locator('canvas.mpl-canvas').bounding_box() + x, y = bbox['x'] + bbox['width'] / 2, bbox['y'] + bbox['height'] / 2 + page.mouse.move(x, y, steps=2) + message = page.locator('span.mpl-message') + expect(message).to_have_text('x=0.500 y=0.500') From 345509382284eb7bc1818ca57e6dc0ca41f78c9a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 2 Aug 2022 23:24:03 -0400 Subject: [PATCH 03/10] WebAgg: Add test for toolbar save button --- lib/matplotlib/tests/test_backend_webagg.py | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 95b2dcea2119..10990433b062 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -147,3 +147,30 @@ def test_webagg_toolbar(page, toolbar): page.mouse.move(x, y, steps=2) message = page.locator('span.mpl-message') expect(message).to_have_text('x=0.500 y=0.500') + + +@pytest.mark.backend('webagg') +def test_webagg_toolbar_save(page): + from playwright.sync_api import expect + + # Listen for all console logs. + page.on('console', lambda msg: print(f'CONSOLE: {msg.text}')) + + fig, ax = plt.subplots(facecolor='w') + + # Don't start the Tornado event loop, but use the existing event loop + # started by the `page` fixture. + WebAggApplication.initialize() + WebAggApplication.started = True + + page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') + + save = page.locator('button.mpl-widget').nth(5) + expect(save).to_be_visible() + + with page.context.expect_page() as new_page_info: + save.click() + new_page = new_page_info.value + + new_page.wait_for_load_state() + assert new_page.url.endswith('download.png') From d6e116499af97f4e2f07b4932c635237aca82a83 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Aug 2022 01:54:31 -0400 Subject: [PATCH 04/10] Handle port passed to WebAggApplication.initialize --- lib/matplotlib/backends/backend_webagg.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index dfc5747ef77c..45a50cb7cb2a 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -234,9 +234,9 @@ def random_ports(port, n): cls.address = mpl.rcParams['webagg.address'] else: cls.address = address - cls.port = mpl.rcParams['webagg.port'] - for port in random_ports(cls.port, - mpl.rcParams['webagg.port_retries']): + if port is None: + port = mpl.rcParams['webagg.port'] + for port in random_ports(port, mpl.rcParams['webagg.port_retries']): try: app.listen(port, cls.address) except OSError as e: From 97259e1aee870e156e74e1717527bdb4c6c87756 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Aug 2022 01:44:30 -0400 Subject: [PATCH 05/10] Use a random port for WebAgg tests This prevents them from conflicting when run in parallel. Even though there is some attempt to pick alternative ports, that seems to cause a socket leak somewhere in Tornado, which Pytest converts to an error, so we have to do it manually. --- lib/matplotlib/tests/conftest.py | 18 ++++++++++++++++++ lib/matplotlib/tests/test_backend_webagg.py | 12 ++++++------ .../tests/test_backends_interactive.py | 7 ++++--- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/tests/conftest.py b/lib/matplotlib/tests/conftest.py index 54a1bc6cae94..a49a97d51c32 100644 --- a/lib/matplotlib/tests/conftest.py +++ b/lib/matplotlib/tests/conftest.py @@ -1,2 +1,20 @@ +import contextlib +import socket + +import pytest + from matplotlib.testing.conftest import ( # noqa mpl_test_settings, pytest_configure, pytest_unconfigure, pd, xr) + + +@pytest.fixture +def random_port(): + with contextlib.closing( + socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('', 0)) + s.listen(1) + addr = s.getsockname() + port = addr[1] + + return port diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 10990433b062..38bed92e24d7 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -53,7 +53,7 @@ def test_webagg_core_no_toolbar(): @pytest.mark.backend('webagg') -def test_webagg_general(page): +def test_webagg_general(random_port, page): from playwright.sync_api import expect # Listen for all console logs. @@ -63,7 +63,7 @@ def test_webagg_general(page): # Don't start the Tornado event loop, but use the existing event loop # started by the `page` fixture. - WebAggApplication.initialize() + WebAggApplication.initialize(port=random_port) WebAggApplication.started = True page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') @@ -89,7 +89,7 @@ def test_webagg_general(page): @pytest.mark.backend('webagg') @pytest.mark.parametrize('toolbar', ['toolbar2', 'toolmanager']) -def test_webagg_toolbar(page, toolbar): +def test_webagg_toolbar(random_port, page, toolbar): from playwright.sync_api import expect # Listen for all console logs. @@ -104,7 +104,7 @@ def test_webagg_toolbar(page, toolbar): # Don't start the Tornado event loop, but use the existing event loop # started by the `page` fixture. - WebAggApplication.initialize() + WebAggApplication.initialize(port=random_port) WebAggApplication.started = True page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') @@ -150,7 +150,7 @@ def test_webagg_toolbar(page, toolbar): @pytest.mark.backend('webagg') -def test_webagg_toolbar_save(page): +def test_webagg_toolbar_save(random_port, page): from playwright.sync_api import expect # Listen for all console logs. @@ -160,7 +160,7 @@ def test_webagg_toolbar_save(page): # Don't start the Tornado event loop, but use the existing event loop # started by the `page` fixture. - WebAggApplication.initialize() + WebAggApplication.initialize(port=random_port) WebAggApplication.started = True page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 6830e7d5c845..1ad3848bde60 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -479,14 +479,15 @@ def test_cross_Qt_imports(host, mpl): @pytest.mark.skipif('TF_BUILD' in os.environ, reason="this test fails an azure for unknown reasons") @pytest.mark.skipif(sys.platform == "win32", reason="Cannot send SIGINT on Windows.") -def test_webagg(): +def test_webagg(random_port): pytest.importorskip("tornado") proc = subprocess.Popen( [sys.executable, "-c", inspect.getsource(_test_interactive_impl) - + "\n_test_interactive_impl()", "{}"], + + "\n_test_interactive_impl()", + json.dumps({'webagg.port': random_port})], env={**os.environ, "MPLBACKEND": "webagg", "SOURCE_DATE_EPOCH": "0"}) - url = f'http://{mpl.rcParams["webagg.address"]}:{mpl.rcParams["webagg.port"]}' + url = f'http://{mpl.rcParams["webagg.address"]}:{random_port}' timeout = time.perf_counter() + _test_timeout try: while True: From 9bd2331f04b1f0946a7993b984d50248ece34cd8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Aug 2022 02:01:01 -0400 Subject: [PATCH 06/10] WebAgg: Add a test for resizing the figure --- lib/matplotlib/tests/test_backend_webagg.py | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 38bed92e24d7..8db49ac688f8 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -87,6 +87,35 @@ def test_webagg_general(random_port, page): raise ImageComparisonFailure(err) +@pytest.mark.backend('webagg') +def test_webagg_resize(random_port, page): + # Listen for all console logs. + page.on('console', lambda msg: print(f'CONSOLE: {msg.text}')) + + fig, ax = plt.subplots(facecolor='w') + orig_bbox = fig.bbox.frozen() + + # Don't start the Tornado event loop, but use the existing event loop + # started by the `page` fixture. + WebAggApplication.initialize(port=random_port) + WebAggApplication.started = True + + page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') + + canvas = page.locator('canvas.mpl-canvas') + + # Resize the canvas to be twice as big. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] - 1, bbox['y'] + bbox['height'] - 1 + page.mouse.move(x, y) + page.mouse.down() + page.mouse.move(x + bbox['width'], y + bbox['height']) + page.mouse.up() + + assert fig.bbox.height == orig_bbox.height * 2 + assert fig.bbox.width == orig_bbox.width * 2 + + @pytest.mark.backend('webagg') @pytest.mark.parametrize('toolbar', ['toolbar2', 'toolmanager']) def test_webagg_toolbar(random_port, page, toolbar): From 99b815e2beca70bfc265b83247596a61793b4b95 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Aug 2022 04:10:17 -0400 Subject: [PATCH 07/10] WebAgg: Add tests for pan/zoom tool --- lib/matplotlib/tests/test_backend_webagg.py | 116 ++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 8db49ac688f8..9ece1f85fbca 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -1,4 +1,5 @@ import os +import re import shutil import subprocess import sys @@ -203,3 +204,118 @@ def test_webagg_toolbar_save(random_port, page): new_page.wait_for_load_state() assert new_page.url.endswith('download.png') + + +@pytest.mark.backend('webagg') +def test_webagg_toolbar_pan(random_port, page): + from playwright.sync_api import expect + + # Listen for all console logs. + page.on('console', lambda msg: print(f'CONSOLE: {msg.text}')) + + fig, ax = plt.subplots(facecolor='w') + ax.plot([3, 2, 1]) + orig_lim = ax.viewLim.frozen() + # Make figure coords ~= axes coords, with ticks visible for inspection. + ax.set_position([0, 0, 1, 1]) + ax.tick_params(axis='y', direction='in', pad=-22) + ax.tick_params(axis='x', direction='in', pad=-15) + + # Don't start the Tornado event loop, but use the existing event loop + # started by the `page` fixture. + WebAggApplication.initialize() + WebAggApplication.started = True + + page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') + + canvas = page.locator('canvas.mpl-canvas') + expect(canvas).to_be_visible() + home = page.locator('button.mpl-widget').nth(0) + expect(home).to_be_visible() + pan = page.locator('button.mpl-widget').nth(3) + expect(pan).to_be_visible() + zoom = page.locator('button.mpl-widget').nth(4) + expect(zoom).to_be_visible() + + active_re = re.compile(r'active') + expect(pan).not_to_have_class(active_re) + expect(zoom).not_to_have_class(active_re) + assert ax.get_navigate_mode() is None + pan.click() + expect(pan).to_have_class(active_re) + expect(zoom).not_to_have_class(active_re) + assert ax.get_navigate_mode() == 'PAN' + + # Pan 50% of the figure diagonally toward bottom-right. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down() + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up() + + assert ax.get_xlim() == (orig_lim.x0 - orig_lim.width / 2, + orig_lim.x1 - orig_lim.width / 2) + assert ax.get_ylim() == (orig_lim.y0 + orig_lim.height / 2, + orig_lim.y1 + orig_lim.height / 2) + + # Reset. + home.click() + assert ax.viewLim.bounds == orig_lim.bounds + + # Pan 50% of the figure diagonally toward bottom-right, while holding 'x' + # key, to constrain the pan horizontally. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down() + page.keyboard.down('x') + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up() + page.keyboard.up('x') + + assert ax.get_xlim() == (orig_lim.x0 - orig_lim.width / 2, + orig_lim.x1 - orig_lim.width / 2) + assert ax.get_ylim() == (orig_lim.y0, orig_lim.y1) + + # Reset. + home.click() + assert ax.viewLim.bounds == orig_lim.bounds + + # Pan 50% of the figure diagonally toward bottom-right, while holding 'y' + # key, to constrain the pan vertically. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down() + page.keyboard.down('y') + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up() + page.keyboard.up('y') + + assert ax.get_xlim() == (orig_lim.x0, orig_lim.x1) + assert ax.get_ylim() == (orig_lim.y0 + orig_lim.height / 2, + orig_lim.y1 + orig_lim.height / 2) + + # Reset. + home.click() + assert ax.viewLim.bounds == orig_lim.bounds + + # Zoom 50% of the figure diagonally toward bottom-right. + bbox = canvas.bounding_box() + x, y = bbox['x'], bbox['y'] + page.mouse.move(x, y) + page.mouse.down(button='right') + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up(button='right') + + # Expands in x-direction. + assert ax.viewLim.x0 == orig_lim.x0 + assert ax.viewLim.x1 < orig_lim.x1 - orig_lim.width / 2 + # Contracts in y-direction. + assert ax.viewLim.y1 == orig_lim.y1 + assert ax.viewLim.y0 < orig_lim.y0 - orig_lim.height / 2 From 7faa294a5828b1098feb99d63077bc4a86c65d9b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Aug 2022 04:21:10 -0400 Subject: [PATCH 08/10] WebAgg: Add tests of the zoom-to-rect tool --- lib/matplotlib/tests/test_backend_webagg.py | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 9ece1f85fbca..1096c461a668 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -319,3 +319,118 @@ def test_webagg_toolbar_pan(random_port, page): # Contracts in y-direction. assert ax.viewLim.y1 == orig_lim.y1 assert ax.viewLim.y0 < orig_lim.y0 - orig_lim.height / 2 + + +@pytest.mark.backend('webagg') +def test_webagg_toolbar_zoom(random_port, page): + from playwright.sync_api import expect + + # Listen for all console logs. + page.on('console', lambda msg: print(f'CONSOLE: {msg.text}')) + + fig, ax = plt.subplots(facecolor='w') + ax.plot([3, 2, 1]) + orig_lim = ax.viewLim.frozen() + # Make figure coords ~= axes coords, with ticks visible for inspection. + ax.set_position([0, 0, 1, 1]) + ax.tick_params(axis='y', direction='in', pad=-22) + ax.tick_params(axis='x', direction='in', pad=-15) + + # Don't start the Tornado event loop, but use the existing event loop + # started by the `page` fixture. + WebAggApplication.initialize() + WebAggApplication.started = True + + page.goto(f'http://{WebAggApplication.address}:{WebAggApplication.port}/') + + canvas = page.locator('canvas.mpl-canvas') + expect(canvas).to_be_visible() + home = page.locator('button.mpl-widget').nth(0) + expect(home).to_be_visible() + pan = page.locator('button.mpl-widget').nth(3) + expect(pan).to_be_visible() + zoom = page.locator('button.mpl-widget').nth(4) + expect(zoom).to_be_visible() + + active_re = re.compile(r'active') + expect(pan).not_to_have_class(active_re) + expect(zoom).not_to_have_class(active_re) + assert ax.get_navigate_mode() is None + zoom.click() + expect(pan).not_to_have_class(active_re) + expect(zoom).to_have_class(active_re) + assert ax.get_navigate_mode() == 'ZOOM' + + # Zoom 25% in on each side. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down() + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up() + + assert ax.get_xlim() == (orig_lim.x0 + orig_lim.width / 4, + orig_lim.x1 - orig_lim.width / 4) + assert ax.get_ylim() == (orig_lim.y0 + orig_lim.height / 4, + orig_lim.y1 - orig_lim.height / 4) + + # Reset. + home.click() + + # Zoom 25% in on each side, while holding 'x' key, to constrain the zoom + # horizontally.. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down() + page.keyboard.down('x') + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up() + page.keyboard.up('x') + + assert ax.get_xlim() == (orig_lim.x0 + orig_lim.width / 4, + orig_lim.x1 - orig_lim.width / 4) + assert ax.get_ylim() == (orig_lim.y0, orig_lim.y1) + + # Reset. + home.click() + + # Zoom 25% in on each side, while holding 'y' key, to constrain the zoom + # vertically. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down() + page.keyboard.down('y') + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up() + page.keyboard.up('y') + + assert ax.get_xlim() == (orig_lim.x0, orig_lim.x1) + assert ax.get_ylim() == (orig_lim.y0 + orig_lim.height / 4, + orig_lim.y1 - orig_lim.height / 4) + + # Reset. + home.click() + + # Zoom 25% out on each side. + bbox = canvas.bounding_box() + x, y = bbox['x'] + bbox['width'] / 4, bbox['y'] + bbox['height'] / 4 + page.mouse.move(x, y) + page.mouse.down(button='right') + page.mouse.move(x + bbox['width'] / 2, y + bbox['height'] / 2, + steps=20) + page.mouse.up(button='right') + + # Limits were doubled, but based on the central point. + cx = orig_lim.x0 + orig_lim.width / 2 + x0 = cx - orig_lim.width + x1 = cx + orig_lim.width + assert ax.get_xlim() == (x0, x1) + cy = orig_lim.y0 + orig_lim.height / 2 + y0 = cy - orig_lim.height + y1 = cy + orig_lim.height + assert ax.get_ylim() == (y0, y1) From 785b169470928ad8628be8d66deb24531d4d6991 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 4 Oct 2022 04:07:10 -0400 Subject: [PATCH 09/10] ci: Explicitly ask for all browsers --- .github/workflows/tests.yml | 7 ++++--- azure-pipelines.yml | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 72936870b803..36a2476457f6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -197,10 +197,10 @@ jobs: ~/.cache/matplotlib !~/.cache/matplotlib/tex.cache !~/.cache/matplotlib/test_cache - key: 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} + key: 5-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} restore-keys: | - 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- - 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl- + 5-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- + 5-${{ runner.os }}-py${{ matrix.python-version }}-mpl- - name: Install Python dependencies run: | @@ -314,6 +314,7 @@ jobs: - name: Run pytest run: | pytest -rfEsXR -n auto \ + --browser chromium --browser firefox --browser webkit \ --maxfail=50 --timeout=300 --durations=25 \ --cov-report=xml --cov=lib --log-level=DEBUG --color=yes diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 14bcc7125d84..bc48d870740e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -228,6 +228,7 @@ stages: fi PYTHONFAULTHANDLER=1 pytest -rfEsXR -n 2 \ --maxfail=50 --timeout=300 --durations=25 \ + --browser chromium --browser firefox --browser webkit \ --junitxml=junit/test-results.xml --cov-report=xml --cov=lib if [[ -n $SESSION_ID ]]; then if [[ $VS_VER == 2022 ]]; then From f63df0d3841918841f73764445af1faeb6a2ed81 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Nov 2022 03:33:13 -0400 Subject: [PATCH 10/10] Debug other OSs --- .github/workflows/tests.yml | 7 +++++++ lib/matplotlib/tests/test_backend_webagg.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 36a2476457f6..a9331ae7ebb4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -315,6 +315,7 @@ jobs: run: | pytest -rfEsXR -n auto \ --browser chromium --browser firefox --browser webkit \ + --slowmo=100 --tracing=on --video=on \ --maxfail=50 --timeout=300 --durations=25 \ --cov-report=xml --cov=lib --log-level=DEBUG --color=yes @@ -381,6 +382,12 @@ jobs: name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }} result images" path: ./result_images + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }} playwright" + path: ./test-results + # Separate dependent job to only upload one issue from the matrix of jobs create-issue: if: ${{ failure() && github.event_name == 'schedule' }} diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 1096c461a668..cc1f9d2e5a4c 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -105,12 +105,16 @@ def test_webagg_resize(random_port, page): canvas = page.locator('canvas.mpl-canvas') + print(f'{orig_bbox=}') # Resize the canvas to be twice as big. bbox = canvas.bounding_box() + print(f'{bbox=}') x, y = bbox['x'] + bbox['width'] - 1, bbox['y'] + bbox['height'] - 1 + print(f'{x=} {y=}') page.mouse.move(x, y) page.mouse.down() page.mouse.move(x + bbox['width'], y + bbox['height']) + print(f'{x + bbox["width"]=} {y + bbox["height"]=}') page.mouse.up() assert fig.bbox.height == orig_bbox.height * 2