feat(e2e): add what only Windows checks as extras

padding (the border's 7 px), cloaking (and what stop does to it), the
config file watch, the system's window tracking behind focus follows
mouse, and a second display with a window sent to it. Each is run after
the tiling windows as windows_<name>, its checks labelled [windows].
This commit is contained in:
LGUG2Z committed 2026-10-30 18:22:44 -07:00
1 parent 2f1df7decd
commit 7054d02110
7 files changed
+234 -3

No files matched your search

Whitespace-only changes.
@@ -0,0 +1,44 @@
"""How kfw puts windows away, and what `komorebic stop` does to them."""
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.platform import Client
from ..windows_platform import WindowsPlatform
def _flags(platform: WindowsPlatform, client: Client) -> dict:
window = platform.os_window_by_title(client.title) or {}
return {"cloaked": window.get("cloaked"), "minimized": window.get("minimized")}
def run(platform: WindowsPlatform, checks: Checks, clients: list[Client]) -> None:
"""Sends the last of `clients` to workspace 1 and minimizes the first,
stops the daemon and starts it again. The daemon has `clients` tiled; it
runs at the end."""
away, minimized = clients[-1], clients[0]
platform.activate(away)
platform.wait_for(lambda: True, 1)
platform.komorebic("send-to-workspace", "1")
checks.check(
"a window on another workspace is cloaked, not minimized",
platform.wait_for(lambda: platform.hidden(away))
and _flags(platform, away)["minimized"] is False,
)
platform.minimize(minimized)
checks.check(
"a minimized window is minimized, not cloaked",
platform.wait_for(lambda: _flags(platform, minimized)["minimized"] is True)
and _flags(platform, minimized)["cloaked"] is False,
)
platform.komorebic("stop")
platform.wait_for(lambda: platform.komorebic_status("state")[0] != 0, 20)
checks.check(
"after `komorebic stop` the window that was put away is not cloaked",
platform.wait_for(lambda: _flags(platform, away)["cloaked"] is False),
)
checks.check(
"... and the minimized one is still minimized",
_flags(platform, minimized)["minimized"] is True,
)
platform.start_komorebi()
@@ -0,0 +1,36 @@
"""kfw watches the configuration file it was started with."""
import json
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.platform import Client
from ..windows_platform import CONFIG_PATH, WindowsPlatform
def _config(name: str) -> dict:
return {"monitors": [{"workspaces": [{"name": name, "layout": "BSP"}]}]}
def _name(platform: WindowsPlatform) -> str | None:
return platform.state().monitors[0].workspaces[0].name
def run(platform: WindowsPlatform, checks: Checks, clients: list[Client]) -> None:
"""Starts the daemon from a file and changes the file on disk. The daemon
runs at the end, on the new file."""
platform.restart_with(_config("before"))
checks.check("the daemon starts with the name in the file", _name(platform) == "before")
changed = platform._out / "komorebi-changed.json"
changed.write_text(json.dumps(_config("after")))
platform._agent.put_file(changed, CONFIG_PATH)
checks.check(
"a changed file is read by the daemon on its own",
platform.wait_for(lambda: _name(platform) == "after", timeout=30),
)
platform.komorebic("replace-configuration", CONFIG_PATH)
checks.check(
"`replace-configuration` reads it again and the name stays",
platform.wait_for(lambda: _name(platform) == "after", timeout=10),
)
@@ -0,0 +1,46 @@
"""What kfw adds to the padding its state names: the room of the border."""
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.platform import Client
from ..windows_platform import WindowsPlatform
# kfw pads a window by the width of its border plus the border's offset when
# borders are on, which they are by default (border_manager: 8 and -1)
BORDER_ROOM = 8 - 1
def run(platform: WindowsPlatform, checks: Checks, clients: list[Client]) -> None:
"""Measures how far the tiled windows of the first workspace are from the
edges of the work area and from each other, against the paddings the
state gives. The daemon has `clients` tiled, with its default padding."""
workspace = platform.state().workspace()
padding = workspace.workspace_padding
container = workspace.container_padding
if padding is None or container is None:
checks.skip("the state gives the paddings", "it does not")
return
area = platform.work_area()
frames = [w.frame for c in clients if (w := platform.os_window(c)) is not None]
inset = padding + container + BORDER_ROOM
edges = (
min(f.x for f in frames) - area.x,
min(f.y for f in frames) - area.y,
area.x + area.w - max(f.x + f.w for f in frames),
area.y + area.h - max(f.y + f.h for f in frames),
)
print(f" paddings {padding}/{container}, edges {edges}")
checks.check(
f"the windows are {inset} px from every edge: both paddings and "
f"{BORDER_ROOM} px for the border",
all(edge == inset for edge in edges),
)
first = min(frames, key=lambda f: f.x)
neighbours = [f for f in frames if f.x > first.x and f.y < first.y + first.h
and f.y + f.h > first.y]
if neighbours:
gap = min(f.x for f in neighbours) - (first.x + first.w)
checks.check(
f"two windows side by side are {2 * (container + BORDER_ROOM)} px apart",
gap == 2 * (container + BORDER_ROOM),
)
@@ -0,0 +1,50 @@
"""A second display, before any is unplugged: its monitor and a window sent to it."""
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.platform import Client
from ..windows_platform import WindowsPlatform
def run(platform: WindowsPlatform, checks: Checks, clients: list[Client]) -> None:
"""Plugs in a second display, checks the monitor the daemon has for it,
sends the last of `clients` there and checks where it is and what has
the focus. The display stays plugged in."""
platform.add_display()
checks.check(
"the daemon has a monitor for the second display",
platform.wait_for(lambda: len(platform.state().monitors) == 2, timeout=30),
)
displays = platform.displays()
sizes = [(m.size.w, m.size.h) for m in platform.state().monitors]
checks.check(
"each monitor has the size of its display",
sizes == [(d.w, d.h) for d in displays],
)
moved = clients[-1]
platform.activate(moved)
platform.komorebic("move-to-monitor", "1")
checks.check(
"`move-to-monitor 1` takes the window to the second monitor",
platform.wait_for(lambda: platform.state().workspace_of(moved.title, monitor=1) == 0),
)
checks.check(
"... where the operating system has it inside the second display",
platform.wait_for(
lambda: (w := platform.os_window(moved)) is not None
and w.frame.inside(displays[1], 1)
),
)
checks.check(
"... and the others stay on the first monitor",
all(platform.state().workspace_of(c.title, monitor=0) is not None
for c in clients[:-1]),
)
checks.check(
"the daemon's focus is the operating system's: the moved window",
platform.wait_for(
lambda: platform.state().focused_title(monitor=1) == moved.title
and (w := platform.os_window(moved)) is not None and w.focused
),
)
@@ -0,0 +1,34 @@
"""kfw's focus follows mouse is the setting of Windows itself."""
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.platform import Client
from ..windows_platform import WindowsPlatform
# SPI_GETACTIVEWINDOWTRACKING
GET = (
"Add-Type -Name S -Namespace K -MemberDefinition "
"'[DllImport(\"user32.dll\")] public static extern bool "
"SystemParametersInfo(uint a, uint b, ref int c, uint d);'; "
"$v = 0; [void][K.S]::SystemParametersInfo(0x1000, 0, [ref]$v, 0); $v"
)
def _tracking(platform: WindowsPlatform) -> str:
return platform._agent.exec("powershell", "-NoProfile", "-Command", GET)["stdout"].strip()
def run(platform: WindowsPlatform, checks: Checks, clients: list[Client]) -> None:
"""Turns focus follows mouse on and off and reads the system's active
window tracking each time. It is left off."""
checks.check("window tracking is off to begin with", _tracking(platform) == "0")
platform.komorebic("focus-follows-mouse", "enable")
checks.check(
"`focus-follows-mouse enable` turns on the system's window tracking",
platform.wait_for(lambda: _tracking(platform) == "1"),
)
platform.komorebic("focus-follows-mouse", "disable")
checks.check(
"`focus-follows-mouse disable` turns it off",
platform.wait_for(lambda: _tracking(platform) == "0"),
)
+24 -3
View File
@@ -37,6 +37,7 @@ import zipfile
from pathlib import Path
from komorebi_vm.core.checks import Checks
from komorebi_vm.core.suite import platform_only, run_suite, shared
from .agent import AgentError, AgentLost
from .guest import Guest
@@ -124,6 +125,13 @@ WITH_CLIENTS = {
"scrolling", "locking",
}
# what only Windows can check, `extras/<name>.py`, after the tiling windows
EXTRAS = {
"windows_padding": "padding", "windows_cloaking": "cloaking",
"windows_config_file": "config_file", "windows_tracking": "tracking",
"windows_second_display": "second_display",
}
# a small configuration for the sweep's `replace-configuration`
SWEEP_CONFIG = '{"monitors":[{"workspaces":[{"name":"again","layout":"BSP"}]}]}'
SWEEP_CONFIG_PATH = "C:\\e2e\\sweep.json"
@@ -229,9 +237,11 @@ def run_one(
) -> RecordedChecks:
out.mkdir(parents=True, exist_ok=True)
module = "commands" if name == "unknown_monitor" else name
scenario = importlib.import_module(
f"komorebi_vm.core.scenarios.{module}"
)
scenario = None
if name not in EXTRAS:
scenario = importlib.import_module(
f"komorebi_vm.core.scenarios.{module}"
)
tiling = importlib.import_module("komorebi_vm.core.scenarios.tiling")
capture = os.environ.get("KOMOREBI_E2E_CAPTURE")
@@ -256,6 +266,17 @@ def run_one(
if recorder:
_start_recorder(platform, Path(recorder), guest, out)
try:
if name in EXTRAS:
extra = importlib.import_module(
f"platforms.qemu_windows.extras.{EXTRAS[name]}"
)
clients = run_suite(
checks, shared(tiling.run, platform, checks)
)[0]
run_suite(checks, platform_only(
"windows", extra.run, platform, checks, clients
))
return checks
if name in WITH_CLIENTS:
clients = tiling.run(platform, checks)
if capture and name == "stacks":