Skip to content

Commit 709588f

Browse files
smeagol91Jenkins
authored andcommitted
igloo: cover the skeleton-to-canvas transition in the browser
Implements L-01 and drops its skip (CMK-37022). It is the one browser backstop for the area; the other cases are covered in Vitest, so their skeletons go, as does T-01, whose affordance left the design. The routes are collected and continued by hand, since a sleeping handler would hold the assertions back too, and the handler stops holding rather than being unrouted, which would cancel the invocations still pending in it. Change-Id: Icdf0452efe85a3d0dce6126ec1dc2281954bdc01
1 parent d4c9707 commit 709588f

2 files changed

Lines changed: 89 additions & 104 deletions

File tree

tests/system/gui/test_graph_loading_state.py

Lines changed: 81 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3,129 +3,106 @@
33
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
44
# conditions defined in the file COPYING, which is part of this source code package.
55

6-
"""Graph loading and error states (R1.4). Skipped skeletons (CMK-35973).
6+
"""Graph loading and error states (R1.4).
77
8-
Complete once the engine renders: drive the load window with page.route on the graph
9-
time-series endpoint (hold or error it). Indicator follows containment - skeleton on pages,
10-
spinner in dashboard widgets (CMK-35972 foundation).
8+
L-01 is the browser backstop for the whole area: it is the one test proving the
9+
skeleton -> canvas transition wires up against a real fetch. Everything else the test plan
10+
lists here - the fast load, the widgets' spinner and its containment, the error states and
11+
their retry - is component-level behaviour and is covered in the Vitest suites
12+
(`GraphGroup.test.ts`, `DashboardContentGraph.test.ts`), where the pending, 500 and 404
13+
responses can be driven exactly.
14+
15+
C-01 to C-04 extend the same concern to the remaining surfaces and are skipped
16+
skeletons until the engine renders on them.
1117
"""
1218

19+
from collections.abc import Iterator
20+
1321
import pytest
22+
from playwright.sync_api import expect, Route
1423

24+
from tests.system.gui.testlib.playwright.pom.graphing.timeseries_graph import ServiceGraphs
1525
from tests.system.gui.testlib.playwright.pom.monitor.dashboard import MainDashboard
26+
from tests.system.gui.testlib.playwright.pom.monitor.service import ServicePage
27+
from tests.system.gui.testlib.playwright.pom.monitor.services_of_host import ServicesOfHostPage
1628
from tests.testlib.graphing import SKIP_PENDING_GRAPH_ENGINE
1729

30+
# The endpoint every rendered graph fetches its data from; holding it holds the load window open.
31+
_GRAPH_DATA_URL = "**/domain-types/graph/actions/fetch_data/invoke"
1832

19-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
20-
def test_skeleton_visible_while_loading_then_canvas(
21-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
22-
) -> None:
23-
"""L-01 (R1.4 Area 1): a page skeleton shows during load, then the canvas.
24-
25-
Do: hold the time-series response ~2s; open the service detail page.
26-
Assert: skeleton visible during the delay; after it, skeleton gone, canvas visible, no
27-
console errors.
28-
"""
29-
pytest.fail("CMK-35973 skeleton: body not implemented")
30-
31-
32-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
33-
def test_no_skeleton_on_fast_load(
34-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
35-
) -> None:
36-
"""L-02 (R1.4 Area 1): on a fast load the skeleton is not left visible.
37-
38-
Do: open the service detail page without delaying the route; wait for render.
39-
Assert: skeleton not visible once the canvas is; no error state; no JS errors.
40-
"""
41-
pytest.fail("CMK-35973 skeleton: body not implemented")
42-
43-
44-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
45-
def test_dashboard_widget_shows_spinner_not_skeleton(
46-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
47-
) -> None:
48-
"""S-01 (R1.4 Area 2): a dashboard graph widget shows a spinner, not a skeleton.
49-
50-
Do: hold the time-series response ~2s; open a dashboard with a graph widget.
51-
Assert: spinner (not skeleton) visible during the delay; after it, spinner gone, canvas
52-
visible.
53-
"""
54-
pytest.fail("CMK-35973 skeleton: body not implemented")
55-
56-
57-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
58-
def test_each_dashboard_widget_loads_independently(
59-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
60-
) -> None:
61-
"""S-02 (R1.4 Area 2): each dashboard graph widget shows/resolves its own spinner.
62-
63-
Do: observe multiple graph widgets on one dashboard during the delayed load.
64-
Assert: each has its own spinner (not one shared) and transitions to its own canvas.
65-
"""
66-
pytest.fail("CMK-35973 skeleton: body not implemented")
67-
33+
# The same service the canvas interaction tests drive: several graphs from one /proc/meminfo, so
34+
# the group has more than one panel to skeletonise.
35+
SERVICE_WITH_GRAPHS = "Memory"
6836

69-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
70-
def test_error_state_for_http_500(
71-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
72-
) -> None:
73-
"""E-01 (R1.4 Area 3): HTTP 500 renders a 5xx-category error state.
7437

75-
Do: fulfil the time-series request with HTTP 500; open the service detail page.
76-
Assert: error-state element visible with a 5xx message (no raw traceback/code); no
77-
unhandled JS exception.
78-
"""
79-
pytest.fail("CMK-35973 skeleton: body not implemented")
38+
@pytest.fixture(name="javascript_errors")
39+
def fixture_javascript_errors(dashboard_page: MainDashboard) -> Iterator[list[str]]:
40+
"""Uncaught page errors raised while the test runs."""
41+
errors: list[str] = []
42+
dashboard_page.page.on("pageerror", lambda error: errors.append(str(error)))
43+
yield errors
8044

8145

82-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
83-
def test_error_state_for_http_404(
84-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
85-
) -> None:
86-
"""E-02 (R1.4 Area 3): HTTP 404 renders a 4xx-category error state.
87-
88-
Do: fulfil the time-series request with HTTP 404.
89-
Assert: error-state element visible with a 4xx message; no unhandled JS exception.
90-
"""
91-
pytest.fail("CMK-35973 skeleton: body not implemented")
46+
def _open_service_graphs(dashboard_page: MainDashboard, host_name: str) -> ServiceGraphs:
47+
"""Navigate to the service detail page without waiting for its graphs to render."""
48+
services_of_host = ServicesOfHostPage(dashboard_page.page, host_name=host_name)
49+
services_of_host.services_table.host_services_table(host_name).get_by_role(
50+
"link", name=SERVICE_WITH_GRAPHS, exact=True
51+
).click()
52+
return ServiceGraphs(
53+
ServicePage(
54+
dashboard_page.page,
55+
host_name=host_name,
56+
service_name=SERVICE_WITH_GRAPHS,
57+
navigate_to_page=False,
58+
)
59+
)
9260

9361

94-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
95-
def test_retry_control_refetches_and_renders(
96-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
97-
) -> None:
98-
"""E-03 (R1.4 Area 3): the retry control re-requests and renders on success.
99-
100-
Do: induce the error state (HTTP 500), clear the route, click retry.
101-
Assert: graph-data called again; graph renders; error-state no longer visible.
102-
"""
103-
pytest.fail("CMK-35973 skeleton: body not implemented")
104-
105-
106-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
107-
def test_dashboard_widget_error_is_contained(
108-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
62+
def test_skeleton_visible_while_loading_then_canvas(
63+
dashboard_page: MainDashboard,
64+
graph_hosts_with_varying_data: list[str],
65+
javascript_errors: list[str],
10966
) -> None:
110-
"""E-04 (R1.4 Area 3): a widget data error stays in its frame and offers retry.
111-
112-
Do: fulfil the time-series request for one dashboard graph widget with HTTP 500.
113-
Assert: error state visible in the widget with retry; adjacent widgets unaffected.
114-
"""
115-
pytest.fail("CMK-35973 skeleton: body not implemented")
116-
67+
"""L-01 (R1.4 Area 1): a page skeleton shows during load, then the canvas.
11768
118-
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)
119-
def test_still_loading_message_after_threshold(
120-
dashboard_page: MainDashboard, graph_hosts_with_varying_data: list[str]
121-
) -> None:
122-
"""T-01 (R1.4 Area 4): a "still loading" message appears after 10s. Long-running (~12s).
69+
The requests are collected and continued by hand rather than delayed inside the handler:
70+
the sync API runs handlers on the thread the assertions need, so a sleeping handler would
71+
hold the assertions back exactly as long as it holds the response.
12372
124-
Do: hold the time-series response ~12s; open the service detail page.
125-
Assert: 1-10s skeleton only; after 10s "still loading" shows alongside it; after release
126-
it disappears and the canvas shows.
73+
The handler stops holding rather than being unrouted: `unroute` cancels the invocations
74+
still pending in it, which marks their routes handled and leaves nothing left to continue.
12775
"""
128-
pytest.fail("CMK-35973 skeleton: body not implemented")
76+
held: list[Route] = []
77+
holding = True
78+
79+
def hold(route: Route) -> None:
80+
# A refresh landing after the release must not be held, or it would pend to teardown.
81+
if holding:
82+
held.append(route)
83+
else:
84+
route.continue_()
85+
86+
dashboard_page.page.route(_GRAPH_DATA_URL, hold)
87+
88+
graphs = _open_service_graphs(dashboard_page, graph_hosts_with_varying_data[0])
89+
90+
expect(
91+
graphs.skeletons.first,
92+
"No skeleton stood in for the panels while the graph data was still pending",
93+
).to_be_visible()
94+
95+
assert held, "The page never requested its graph data, so nothing was ever held"
96+
holding = False
97+
for route in held:
98+
route.continue_()
99+
100+
graphs.wait_until_rendered()
101+
expect(
102+
graphs.skeletons,
103+
"The skeleton outlived the data it was standing in for",
104+
).to_have_count(0)
105+
assert not javascript_errors, f"The load raised uncaught page errors: {javascript_errors}"
129106

130107

131108
@pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE)

tests/system/gui/testlib/playwright/pom/graphing/timeseries_graph.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,14 @@ def panels(self) -> Locator:
261261
def panel(self, index: int = 0) -> GraphPanel:
262262
return GraphPanel(self.panels.nth(index), self.page, self._main_area)
263263

264+
@property
265+
def skeletons(self) -> Locator:
266+
"""The placeholders standing in for the panels while the first fetch is pending.
267+
268+
Decorative and ``aria-hidden``, so no role query reaches them.
269+
"""
270+
return self._main_area.locator(".graphing-graph-skeleton")
271+
264272
def panel_count(self) -> int:
265273
# ``.count()`` does not auto-wait, so this is only correct once
266274
# ``wait_until_rendered`` has run. The group mounts every panel in one tick, so the

0 commit comments

Comments
 (0)