InkkOops
Authoring scripts
This page describes the script authoring contract in detail: how scripts are registered, how selectors work, when waits are required, and how to choose between pointer actions and semantic actions.
Script contract
A script definition implements IInkkOopsScriptDefinition. In this repository, built-in scripts usually implement IInkkOopsBuiltinScript, which is a host-facing convenience interface.
using InkkSlinger;
public sealed class SidebarRichTextBoxScript : IInkkOopsBuiltinScript
{
public string Name => "sidebar-richtextbox";
public InkkOopsScript CreateScript()
{
return new InkkOopsScriptBuilder(Name)
.WaitForIdle(InkkOopsIdlePolicy.DiagnosticsStable)
.Click("RichTextBox")
.CaptureFrame("after-click")
.DumpTelemetry("after-click")
.Build();
}
}
Name is the public entry point. CreateScript() must return a complete script object.
How scripts are discovered
The runtime depends on the configured script catalog, not on reflection directly. In this repository, the default host profile uses a reflection-backed catalog for convenience, so adding a built-in script class is usually enough to expose it. The important architectural point is that registration is a host concern.
Selectors
Selectors are the preferred way to target elements. They are more stable than coordinates because they survive layout shifts, window size changes, and template changes.
var byName = InkkOopsTargetSelector.Name("CatalogSidebarScrollViewer");
var byAutomationId = InkkOopsTargetSelector.AutomationId("PreviewHost");
var byAutomationName = InkkOopsTargetSelector.AutomationName("RichTextBox");
var scoped =
InkkOopsTargetSelector.Within(
InkkOopsTargetSelector.Name("CatalogSidebarScrollViewer"),
InkkOopsTargetSelector.AutomationName("RichTextBox"));
var indexed =
InkkOopsTargetSelector.DescendantOf(
InkkOopsTargetSelector.Name("ToolbarHost"),
InkkOopsTargetSelector.AutomationName("Delete"))
.WithIndex(1);
The names above come from this repository's Controls Catalog host profile. They are examples, not hard requirements of the InkkOops core.
Resolution rules
- Resolution prefers
x:Name. - If no
x:Namematch exists, automation id is considered next. - If no automation id match exists, automation name is considered next.
- If multiple matches are found and no index is specified, the command fails as
Ambiguous. - If nothing matches, the command fails as
Unresolved.
Wait semantics
Use waits that match the actual condition you need.
WaitForElement: target resolves.WaitForVisible: target resolves and is visible.WaitForEnabled: target resolves and is enabled.WaitForInViewport: target resolves and its action point is in the viewport.WaitForInteractive: target can be used for pointer interaction.WaitForIdle: the host reaches the requested idle policy.
var richTextBoxButton = InkkOopsTargetSelector.Within(
InkkOopsTargetSelector.Name("CatalogSidebarScrollViewer"),
InkkOopsTargetSelector.AutomationName("RichTextBox"));
return new InkkOopsScriptBuilder("sidebar-richtextbox")
.WaitForInteractive(richTextBoxButton, maxFrames: 120)
.Hover(richTextBoxButton)
.Click(richTextBoxButton)
.WaitForIdle(InkkOopsIdlePolicy.DiagnosticsStable)
.Build();
Command composition
This page focuses on how scripts are declared and exposed. For deeper guidance on command families, reduction patterns, pointer-versus-semantic tradeoffs, and proof strategy, read Command patterns.
Pointer commands in authored scripts
Use pointer commands when the bug depends on the actual input path.
return new InkkOopsScriptBuilder("scroll-thumb-drag")
.Hover("ScrollThumb", InkkOopsPointerAnchor.Center)
.PointerDown("ScrollThumb", InkkOopsPointerAnchor.Center)
.Drag("ScrollThumb", 0f, 120f, InkkOopsPointerAnchor.OffsetBy(4f, 4f))
.PointerUp("ScrollThumb", InkkOopsPointerAnchor.OffsetBy(4f, 124f))
.Build();
Pointer diagnostics include the final chosen anchor and root-space action point so you can verify what the runtime actually clicked or hovered.
Semantic commands in authored scripts
Use semantic commands when you want stable control-level behavior and do not need pointer fidelity itself.
var sidebar = InkkOopsTargetSelector.Name("CatalogSidebarScrollViewer");
var target = InkkOopsTargetSelector.Within(
sidebar,
InkkOopsTargetSelector.AutomationName("RichTextBox"));
return new InkkOopsScriptBuilder("semantic-sidebar-nav")
.ScrollIntoView(sidebar, target)
.WaitForInteractive(target)
.Activate(target)
.AssertExists("PreviewHost")
.Build();
Assertions and proof
Assertions prove state. Captures and telemetry prove the visible and structural outcome.
return new InkkOopsScriptBuilder("prove-selection")
.Click("DataGrid")
.WaitForVisible("PreviewHost")
.AssertProperty("SelectionHeader", "Text", "Selected: DataGrid")
.CaptureFrame("datagrid-selected")
.DumpTelemetry("datagrid-selected")
.Build();
Authoring guidelines
- Prefer selectors over coordinates.
- Use explicit waits instead of long chains of
WaitFrames. - Capture proof near the end of the script once the state of interest is stable.
- Use pointer commands for pointer bugs and semantic commands for control-intent bugs.
- Keep scripts short enough that a single failed command still tells you something useful.