|
3 | 3 | # This file is part of Checkmk (https://checkmk.com). It is subject to the terms and |
4 | 4 | # conditions defined in the file COPYING, which is part of this source code package. |
5 | 5 |
|
6 | | -"""Graph loading and error states (R1.4). Skipped skeletons (CMK-35973). |
| 6 | +"""Graph loading and error states (R1.4). |
7 | 7 |
|
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. |
11 | 17 | """ |
12 | 18 |
|
| 19 | +from collections.abc import Iterator |
| 20 | + |
13 | 21 | import pytest |
| 22 | +from playwright.sync_api import expect, Route |
14 | 23 |
|
| 24 | +from tests.system.gui.testlib.playwright.pom.graphing.timeseries_graph import ServiceGraphs |
15 | 25 | 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 |
16 | 28 | from tests.testlib.graphing import SKIP_PENDING_GRAPH_ENGINE |
17 | 29 |
|
| 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" |
18 | 32 |
|
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" |
68 | 36 |
|
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. |
74 | 37 |
|
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 |
80 | 44 |
|
81 | 45 |
|
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 | + ) |
92 | 60 |
|
93 | 61 |
|
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], |
109 | 66 | ) -> 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. |
117 | 68 |
|
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. |
123 | 72 |
|
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. |
127 | 75 | """ |
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}" |
129 | 106 |
|
130 | 107 |
|
131 | 108 | @pytest.mark.skip(reason=SKIP_PENDING_GRAPH_ENGINE) |
|
0 commit comments