test(ui): automation runner gains hover/mousemove — synthetic pointer verify without the OS cursor
Morning gate live-verify apparatus: 'hover element <datId>' and 'mousemove <x> <y>' drive UiRoot.OnMouseMove synthetically (no click, no real cursor theft — a user is present at the machine during this round, unlike the overnight rounds whose drive scripts moved the physical cursor). Deliberately no probe-clock Advance: hover dwell and the world-tooltip timing must ride the production frame tick's real monotonic clock, which keeps running between script commands — an Advance would stamp the idle timestamp with the probe's tiny private counter. (Committed from a re-attached worktree: the round's original worktree was pruned from git's registry mid-session by an external cleanup; the branch and all three finding commits were unaffected.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
942a02af11
commit
302d90209d
2 changed files with 56 additions and 0 deletions
|
|
@ -131,6 +131,32 @@ public sealed class RetailUiAutomationProbe
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2026-08-17 morning gate: synthetic pointer HOVER (no click) at an
|
||||
/// element's center, for rollover/tooltip verification. Deliberately
|
||||
/// does NOT <see cref="Advance"/> the probe's own micro-clock: hover
|
||||
/// dwell (<see cref="UiRoot.MouseIdleMs"/> vs
|
||||
/// <see cref="UiRoot.TooltipDelayMs"/>) and the world-tooltip timing
|
||||
/// must ride the PRODUCTION frame tick's real monotonic clock
|
||||
/// (<c>UiHost.Tick</c>'s <c>TickCount64</c>), which keeps running
|
||||
/// between script commands — an <c>Advance</c> here would stamp the
|
||||
/// idle timestamp with this probe's tiny private counter and make the
|
||||
/// next production tick read an enormous (or negative) idle time.
|
||||
/// </summary>
|
||||
public bool HoverElement(uint datElementId)
|
||||
{
|
||||
var target = FindByDatElementId(datElementId);
|
||||
if (target is null) return Fail($"element 0x{datElementId:X8} not found");
|
||||
_root.OnMouseMove((int)target.CenterX, (int)target.CenterY);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Raw synthetic mouse move (canvas coordinates). Same
|
||||
/// no-<see cref="Advance"/> contract as <see cref="HoverElement"/> —
|
||||
/// used to sweep the pointer across the 3D world for the world-hover
|
||||
/// tooltip's mouse-idle dwell verification.</summary>
|
||||
public void MoveMouse(int x, int y) => _root.OnMouseMove(x, y);
|
||||
|
||||
public bool ClickItem(uint itemGuid, ItemDragSource? sourceKind = null)
|
||||
{
|
||||
var target = FindByItemId(itemGuid, sourceKind);
|
||||
|
|
|
|||
|
|
@ -195,6 +195,8 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
{
|
||||
"dump" => DoDump(),
|
||||
"click" => DoClick(command),
|
||||
"hover" => DoHover(command),
|
||||
"mousemove" => DoMouseMove(command),
|
||||
"doubleclick" => DoDoubleClick(command),
|
||||
"drag" => DoDrag(command),
|
||||
"wait" => DoWait(command),
|
||||
|
|
@ -232,6 +234,34 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
|
|||
return Stop(command, "usage: click element <datId> | click item <guid> [source]");
|
||||
}
|
||||
|
||||
/// <summary>2026-08-17 morning gate: `hover element <datId>` — a
|
||||
/// synthetic pointer hover with NO click, for rollover-state and
|
||||
/// tooltip-dwell verification (the drive scripts must not move the real
|
||||
/// OS cursor when a user is present). See
|
||||
/// <see cref="RetailUiAutomationProbe.HoverElement"/>'s own doc for the
|
||||
/// deliberate no-<c>Advance</c> clock contract.</summary>
|
||||
private bool DoHover(ScriptCommand command)
|
||||
{
|
||||
var p = command.Parts;
|
||||
if (p.Length < 3 || !string.Equals(p[1], "element", StringComparison.OrdinalIgnoreCase))
|
||||
return Stop(command, "usage: hover element <datId>");
|
||||
if (!TryParseUInt(p[2], out uint datId)) return Stop(command, $"bad element id '{p[2]}'");
|
||||
return _probe.HoverElement(datId) || Stop(command, "hover element failed");
|
||||
}
|
||||
|
||||
/// <summary>`mousemove <x> <y>` — raw synthetic pointer move in
|
||||
/// canvas coordinates (world-hover dwell sweeps).</summary>
|
||||
private bool DoMouseMove(ScriptCommand command)
|
||||
{
|
||||
var p = command.Parts;
|
||||
if (p.Length != 3)
|
||||
return Stop(command, "usage: mousemove <x> <y>");
|
||||
if (!TryParseInt(p[1], out int x)) return Stop(command, $"bad x '{p[1]}'");
|
||||
if (!TryParseInt(p[2], out int y)) return Stop(command, $"bad y '{p[2]}'");
|
||||
_probe.MoveMouse(x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool DoDoubleClick(ScriptCommand command)
|
||||
{
|
||||
var p = command.Parts;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue