ha-testcontainer 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/PKG-INFO +1 -1
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/visual/scenario_runner.py +29 -3
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/PKG-INFO +1 -1
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/pyproject.toml +1 -1
- ha_testcontainer-2.3.0/tests/test_scenario_runner_unit.py +227 -0
- ha_testcontainer-2.2.0/tests/test_scenario_runner_unit.py +0 -84
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/LICENSE +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/README.md +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/__init__.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/container.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/ha_server.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/plugins.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/pytest_plugin.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/visual/__init__.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/visual/cursors.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/visual/lovelace_helpers.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/SOURCES.txt +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/dependency_links.txt +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/entry_points.txt +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/requires.txt +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/top_level.txt +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/setup.cfg +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/tests/test_container.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/tests/test_container_unit.py +0 -0
- {ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/tests/test_plugins_unit.py +0 -0
{ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/visual/scenario_runner.py
RENAMED
|
@@ -524,6 +524,9 @@ REPO_ROOT: Path = Path.cwd()
|
|
|
524
524
|
# They participate in ``test_doc_images.py`` but not in ``test_scenarios.py``.
|
|
525
525
|
DOCS_SCENARIOS_DIR: Path = REPO_ROOT / "docs" / "scenarios"
|
|
526
526
|
|
|
527
|
+
# Background color for normalized doc_animation canvases.
|
|
528
|
+
CANVAS_BACKGROUND_RGBA: tuple[int, int, int, int] = (255, 255, 255, 255)
|
|
529
|
+
|
|
527
530
|
# ---------------------------------------------------------------------------
|
|
528
531
|
# Extension registry
|
|
529
532
|
# ---------------------------------------------------------------------------
|
|
@@ -2005,7 +2008,7 @@ def capture_doc_animation(
|
|
|
2005
2008
|
_inject_click_circle(page)
|
|
2006
2009
|
n: int = seg.get("frames", 10)
|
|
2007
2010
|
for i in range(n):
|
|
2008
|
-
|
|
2011
|
+
raw_frame_images.append(take_frame(fixed_clip))
|
|
2009
2012
|
if i < n - 1:
|
|
2010
2013
|
page.wait_for_timeout(interval_ms)
|
|
2011
2014
|
|
|
@@ -2019,7 +2022,7 @@ def capture_doc_animation(
|
|
|
2019
2022
|
|
|
2020
2023
|
# --- capture frames ---
|
|
2021
2024
|
# Each frame is an Image.Image object (PIL dynamically imported above).
|
|
2022
|
-
|
|
2025
|
+
raw_frame_images: list[Any] = []
|
|
2023
2026
|
|
|
2024
2027
|
segments = doc_animation.get("segments")
|
|
2025
2028
|
if segments is not None:
|
|
@@ -2038,7 +2041,7 @@ def capture_doc_animation(
|
|
|
2038
2041
|
if cursor_type:
|
|
2039
2042
|
_inject_cursor(page, cursor_type)
|
|
2040
2043
|
for i in range(frame_count):
|
|
2041
|
-
|
|
2044
|
+
raw_frame_images.append(take_frame(clip))
|
|
2042
2045
|
if i < frame_count - 1:
|
|
2043
2046
|
page.wait_for_timeout(interval_ms)
|
|
2044
2047
|
|
|
@@ -2048,6 +2051,28 @@ def capture_doc_animation(
|
|
|
2048
2051
|
_remove_cursor(page)
|
|
2049
2052
|
_remove_click_circle(page)
|
|
2050
2053
|
|
|
2054
|
+
if not raw_frame_images:
|
|
2055
|
+
raise AssertionError(
|
|
2056
|
+
f"Doc animation '{doc_animation['output']}': captured 0 frames. "
|
|
2057
|
+
"Set frames > 0 (or at least one segment with frames > 0)."
|
|
2058
|
+
)
|
|
2059
|
+
|
|
2060
|
+
# --- normalize frame sizes ---
|
|
2061
|
+
# When viewport size changes within one animation, later screenshots may be
|
|
2062
|
+
# smaller than earlier ones. Composite every frame onto a fixed-size canvas
|
|
2063
|
+
# so uncovered regions are explicitly cleared and no stale pixels bleed
|
|
2064
|
+
# through during GIF playback.
|
|
2065
|
+
max_w = max(f.size[0] for f in raw_frame_images)
|
|
2066
|
+
max_h = max(f.size[1] for f in raw_frame_images)
|
|
2067
|
+
frame_images: list[Any] = []
|
|
2068
|
+
for f in raw_frame_images:
|
|
2069
|
+
if f.size == (max_w, max_h):
|
|
2070
|
+
frame_images.append(f)
|
|
2071
|
+
continue
|
|
2072
|
+
canvas = Image.new("RGBA", (max_w, max_h), CANVAS_BACKGROUND_RGBA)
|
|
2073
|
+
canvas.paste(f, (0, 0))
|
|
2074
|
+
frame_images.append(canvas)
|
|
2075
|
+
|
|
2051
2076
|
# --- assemble GIF ---
|
|
2052
2077
|
# Build a global palette by stacking all frames vertically into one image
|
|
2053
2078
|
# so the quantiser sees the actual pixel distribution across the entire
|
|
@@ -2075,6 +2100,7 @@ def capture_doc_animation(
|
|
|
2075
2100
|
loop=0,
|
|
2076
2101
|
duration=interval_ms,
|
|
2077
2102
|
optimize=False,
|
|
2103
|
+
disposal=2,
|
|
2078
2104
|
)
|
|
2079
2105
|
actual_gif = gif_buf.getvalue()
|
|
2080
2106
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-testcontainer"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.0"
|
|
8
8
|
description = "Full test container for Home Assistant with demo config, custom components, and visual testing"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Unit tests for scenario_runner interaction types — no Docker or browser required.
|
|
2
|
+
|
|
3
|
+
Covers interaction dispatch logic using mocks so that a running Playwright
|
|
4
|
+
browser is not needed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
from unittest.mock import MagicMock, call
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
from PIL import Image, ImageSequence
|
|
14
|
+
|
|
15
|
+
import ha_testcontainer.visual.scenario_runner as sr
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Helpers
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _make_page() -> MagicMock:
|
|
24
|
+
"""Return a lightweight mock that records Playwright Page calls."""
|
|
25
|
+
return MagicMock()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# set_viewport interaction
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestSetViewportInteraction:
|
|
34
|
+
"""run_interactions dispatches set_viewport correctly."""
|
|
35
|
+
|
|
36
|
+
def test_set_viewport_calls_set_viewport_size(self):
|
|
37
|
+
page = _make_page()
|
|
38
|
+
scenario = {
|
|
39
|
+
"interactions": [
|
|
40
|
+
{"type": "set_viewport", "width": 375, "height": 812},
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
sr.run_interactions(page, scenario)
|
|
44
|
+
page.set_viewport_size.assert_called_once_with({"width": 375, "height": 812})
|
|
45
|
+
|
|
46
|
+
def test_set_viewport_no_settle_ms_by_default(self):
|
|
47
|
+
page = _make_page()
|
|
48
|
+
scenario = {
|
|
49
|
+
"interactions": [
|
|
50
|
+
{"type": "set_viewport", "width": 1280, "height": 800},
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
sr.run_interactions(page, scenario)
|
|
54
|
+
page.wait_for_timeout.assert_not_called()
|
|
55
|
+
|
|
56
|
+
def test_set_viewport_settle_ms_waits(self):
|
|
57
|
+
page = _make_page()
|
|
58
|
+
scenario = {
|
|
59
|
+
"interactions": [
|
|
60
|
+
{"type": "set_viewport", "width": 768, "height": 1024, "settle_ms": 300},
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
sr.run_interactions(page, scenario)
|
|
64
|
+
page.set_viewport_size.assert_called_once_with({"width": 768, "height": 1024})
|
|
65
|
+
page.wait_for_timeout.assert_called_once_with(300)
|
|
66
|
+
|
|
67
|
+
def test_set_viewport_coerces_string_dimensions(self):
|
|
68
|
+
"""width/height may arrive as strings from YAML and must be coerced to int."""
|
|
69
|
+
page = _make_page()
|
|
70
|
+
scenario = {
|
|
71
|
+
"interactions": [
|
|
72
|
+
{"type": "set_viewport", "width": "390", "height": "844"},
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
sr.run_interactions(page, scenario)
|
|
76
|
+
page.set_viewport_size.assert_called_once_with({"width": 390, "height": 844})
|
|
77
|
+
|
|
78
|
+
def test_unknown_type_raises(self):
|
|
79
|
+
page = _make_page()
|
|
80
|
+
scenario = {
|
|
81
|
+
"interactions": [
|
|
82
|
+
{"type": "not_a_real_type"},
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
with pytest.raises(ValueError, match="Unknown interaction type"):
|
|
86
|
+
sr.run_interactions(page, scenario)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _png_bytes(width: int, height: int, color: tuple[int, int, int]) -> bytes:
|
|
90
|
+
"""Return an in-memory PNG of the requested size and solid RGB color."""
|
|
91
|
+
img = Image.new("RGB", (width, height), color)
|
|
92
|
+
buf = io.BytesIO()
|
|
93
|
+
img.save(buf, format="PNG")
|
|
94
|
+
return buf.getvalue()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _read_gif_frames(path) -> list[Image.Image]:
|
|
98
|
+
"""Return frames from the GIF at *path* as per-frame RGB Pillow images."""
|
|
99
|
+
with Image.open(path) as gif:
|
|
100
|
+
if gif.format != "GIF":
|
|
101
|
+
raise ValueError(f"Expected GIF file, got: {gif.format}")
|
|
102
|
+
return [f.copy().convert("RGB") for f in ImageSequence.Iterator(gif)]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestDocAnimationViewportNormalization:
|
|
106
|
+
"""doc_animation normalizes variable-size viewport frames to one canvas."""
|
|
107
|
+
|
|
108
|
+
def test_large_to_small_viewport_clears_uncovered_area(self, tmp_path, monkeypatch):
|
|
109
|
+
page = _make_page()
|
|
110
|
+
page.screenshot.side_effect = [
|
|
111
|
+
_png_bytes(8, 6, (255, 0, 0)),
|
|
112
|
+
_png_bytes(4, 3, (0, 255, 0)),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
save_kwargs: dict[str, object] = {}
|
|
116
|
+
orig_save = Image.Image.save
|
|
117
|
+
|
|
118
|
+
def _save_with_capture(self, file_obj, format=None, **params):
|
|
119
|
+
save_kwargs.update(params)
|
|
120
|
+
return orig_save(self, file_obj, format=format, **params)
|
|
121
|
+
|
|
122
|
+
monkeypatch.setattr(Image.Image, "save", _save_with_capture)
|
|
123
|
+
monkeypatch.setattr(sr, "REPO_ROOT", tmp_path)
|
|
124
|
+
monkeypatch.setenv("DOC_IMAGE_UPDATE", "1")
|
|
125
|
+
|
|
126
|
+
scenario = {
|
|
127
|
+
"doc_animation": {
|
|
128
|
+
"output": "docs/assets/viewport-large-small.gif",
|
|
129
|
+
"interval_ms": 1,
|
|
130
|
+
"dither": False,
|
|
131
|
+
"segments": [
|
|
132
|
+
{"interactions": [{"type": "set_viewport", "width": 8, "height": 6}], "frames": 1},
|
|
133
|
+
{"interactions": [{"type": "set_viewport", "width": 4, "height": 3}], "frames": 1},
|
|
134
|
+
],
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sr.capture_doc_animation(page, scenario)
|
|
139
|
+
|
|
140
|
+
output = tmp_path / "docs/assets/viewport-large-small.gif"
|
|
141
|
+
frames = _read_gif_frames(output)
|
|
142
|
+
assert [f.size for f in frames] == [(8, 6), (8, 6)]
|
|
143
|
+
# (6,4) sits outside the 4x3 second frame; it must be cleared to white.
|
|
144
|
+
assert frames[1].getpixel((6, 4)) == (255, 255, 255)
|
|
145
|
+
assert save_kwargs.get("disposal") == 2
|
|
146
|
+
assert page.set_viewport_size.call_args_list == [
|
|
147
|
+
call({"width": 8, "height": 6}),
|
|
148
|
+
call({"width": 4, "height": 3}),
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
def test_small_to_large_viewport_uses_max_canvas(self, tmp_path, monkeypatch):
|
|
152
|
+
page = _make_page()
|
|
153
|
+
page.screenshot.side_effect = [
|
|
154
|
+
_png_bytes(4, 3, (0, 255, 0)),
|
|
155
|
+
_png_bytes(8, 6, (0, 0, 255)),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
monkeypatch.setattr(sr, "REPO_ROOT", tmp_path)
|
|
159
|
+
monkeypatch.setenv("DOC_IMAGE_UPDATE", "1")
|
|
160
|
+
|
|
161
|
+
scenario = {
|
|
162
|
+
"doc_animation": {
|
|
163
|
+
"output": "docs/assets/viewport-small-large.gif",
|
|
164
|
+
"interval_ms": 1,
|
|
165
|
+
"dither": False,
|
|
166
|
+
"segments": [
|
|
167
|
+
{"interactions": [{"type": "set_viewport", "width": 4, "height": 3}], "frames": 1},
|
|
168
|
+
{"interactions": [{"type": "set_viewport", "width": 8, "height": 6}], "frames": 1},
|
|
169
|
+
],
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
sr.capture_doc_animation(page, scenario)
|
|
174
|
+
|
|
175
|
+
output = tmp_path / "docs/assets/viewport-small-large.gif"
|
|
176
|
+
frames = _read_gif_frames(output)
|
|
177
|
+
assert [f.size for f in frames] == [(8, 6), (8, 6)]
|
|
178
|
+
# (6,4) sits outside the 4x3 first frame; it must be cleared to white.
|
|
179
|
+
assert frames[0].getpixel((6, 4)) == (255, 255, 255)
|
|
180
|
+
|
|
181
|
+
def test_mixed_segment_interactions_keep_consistent_frame_size(self, tmp_path, monkeypatch):
|
|
182
|
+
page = _make_page()
|
|
183
|
+
page.screenshot.side_effect = [
|
|
184
|
+
_png_bytes(8, 6, (255, 0, 0)),
|
|
185
|
+
_png_bytes(5, 4, (0, 255, 0)),
|
|
186
|
+
_png_bytes(3, 2, (0, 0, 255)),
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
monkeypatch.setattr(sr, "REPO_ROOT", tmp_path)
|
|
190
|
+
monkeypatch.setenv("DOC_IMAGE_UPDATE", "1")
|
|
191
|
+
|
|
192
|
+
scenario = {
|
|
193
|
+
"doc_animation": {
|
|
194
|
+
"output": "docs/assets/viewport-mixed-segments.gif",
|
|
195
|
+
"interval_ms": 1,
|
|
196
|
+
"dither": False,
|
|
197
|
+
"segments": [
|
|
198
|
+
{"interactions": [{"type": "set_viewport", "width": 8, "height": 6}], "frames": 1},
|
|
199
|
+
{"interactions": [{"type": "set_viewport", "width": 5, "height": 4}], "frames": 1},
|
|
200
|
+
{"interactions": [{"type": "set_viewport", "width": 3, "height": 2}], "frames": 1},
|
|
201
|
+
],
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
sr.capture_doc_animation(page, scenario)
|
|
206
|
+
|
|
207
|
+
output = tmp_path / "docs/assets/viewport-mixed-segments.gif"
|
|
208
|
+
frames = _read_gif_frames(output)
|
|
209
|
+
assert [f.size for f in frames] == [(8, 6), (8, 6), (8, 6)]
|
|
210
|
+
# (7,5) is outside both smaller later frames and must be white.
|
|
211
|
+
assert frames[1].getpixel((7, 5)) == (255, 255, 255)
|
|
212
|
+
assert frames[2].getpixel((7, 5)) == (255, 255, 255)
|
|
213
|
+
|
|
214
|
+
def test_raises_when_no_frames_captured(self, tmp_path, monkeypatch):
|
|
215
|
+
page = _make_page()
|
|
216
|
+
monkeypatch.setattr(sr, "REPO_ROOT", tmp_path)
|
|
217
|
+
monkeypatch.setenv("DOC_IMAGE_UPDATE", "1")
|
|
218
|
+
|
|
219
|
+
scenario = {
|
|
220
|
+
"doc_animation": {
|
|
221
|
+
"output": "docs/assets/no-frames.gif",
|
|
222
|
+
"frames": 0,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
with pytest.raises(AssertionError, match="captured 0 frames"):
|
|
227
|
+
sr.capture_doc_animation(page, scenario)
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""Unit tests for scenario_runner interaction types — no Docker or browser required.
|
|
2
|
-
|
|
3
|
-
Covers interaction dispatch logic using mocks so that a running Playwright
|
|
4
|
-
browser is not needed.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from unittest.mock import MagicMock, call
|
|
10
|
-
|
|
11
|
-
import pytest
|
|
12
|
-
|
|
13
|
-
import ha_testcontainer.visual.scenario_runner as sr
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# ---------------------------------------------------------------------------
|
|
17
|
-
# Helpers
|
|
18
|
-
# ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _make_page() -> MagicMock:
|
|
22
|
-
"""Return a lightweight mock that records Playwright Page calls."""
|
|
23
|
-
return MagicMock()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# ---------------------------------------------------------------------------
|
|
27
|
-
# set_viewport interaction
|
|
28
|
-
# ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class TestSetViewportInteraction:
|
|
32
|
-
"""run_interactions dispatches set_viewport correctly."""
|
|
33
|
-
|
|
34
|
-
def test_set_viewport_calls_set_viewport_size(self):
|
|
35
|
-
page = _make_page()
|
|
36
|
-
scenario = {
|
|
37
|
-
"interactions": [
|
|
38
|
-
{"type": "set_viewport", "width": 375, "height": 812},
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
sr.run_interactions(page, scenario)
|
|
42
|
-
page.set_viewport_size.assert_called_once_with({"width": 375, "height": 812})
|
|
43
|
-
|
|
44
|
-
def test_set_viewport_no_settle_ms_by_default(self):
|
|
45
|
-
page = _make_page()
|
|
46
|
-
scenario = {
|
|
47
|
-
"interactions": [
|
|
48
|
-
{"type": "set_viewport", "width": 1280, "height": 800},
|
|
49
|
-
]
|
|
50
|
-
}
|
|
51
|
-
sr.run_interactions(page, scenario)
|
|
52
|
-
page.wait_for_timeout.assert_not_called()
|
|
53
|
-
|
|
54
|
-
def test_set_viewport_settle_ms_waits(self):
|
|
55
|
-
page = _make_page()
|
|
56
|
-
scenario = {
|
|
57
|
-
"interactions": [
|
|
58
|
-
{"type": "set_viewport", "width": 768, "height": 1024, "settle_ms": 300},
|
|
59
|
-
]
|
|
60
|
-
}
|
|
61
|
-
sr.run_interactions(page, scenario)
|
|
62
|
-
page.set_viewport_size.assert_called_once_with({"width": 768, "height": 1024})
|
|
63
|
-
page.wait_for_timeout.assert_called_once_with(300)
|
|
64
|
-
|
|
65
|
-
def test_set_viewport_coerces_string_dimensions(self):
|
|
66
|
-
"""width/height may arrive as strings from YAML and must be coerced to int."""
|
|
67
|
-
page = _make_page()
|
|
68
|
-
scenario = {
|
|
69
|
-
"interactions": [
|
|
70
|
-
{"type": "set_viewport", "width": "390", "height": "844"},
|
|
71
|
-
]
|
|
72
|
-
}
|
|
73
|
-
sr.run_interactions(page, scenario)
|
|
74
|
-
page.set_viewport_size.assert_called_once_with({"width": 390, "height": 844})
|
|
75
|
-
|
|
76
|
-
def test_unknown_type_raises(self):
|
|
77
|
-
page = _make_page()
|
|
78
|
-
scenario = {
|
|
79
|
-
"interactions": [
|
|
80
|
-
{"type": "not_a_real_type"},
|
|
81
|
-
]
|
|
82
|
-
}
|
|
83
|
-
with pytest.raises(ValueError, match="Unknown interaction type"):
|
|
84
|
-
sr.run_interactions(page, scenario)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer/visual/lovelace_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ha_testcontainer-2.2.0 → ha_testcontainer-2.3.0}/ha_testcontainer.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|