mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-14 21:04:04 +01:00
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:
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"),
|
||||
)
|
||||
@@ -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":
|
||||
|
||||
Reference in new issue
Block a user